<?php
/*
 *  This file is part of Urd.
/*  vim:ts=4:expandtab:cindent
 *
 *  Urd is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 3 of the License, or
 *  (at your option) any later version.
 *  Urd is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program. See the file "COPYING". If it does not
 *  exist, see <http://www.gnu.org/licenses/>.
 *
 * $LastChangedDate: 2008-07-19 08:33:18 +0200 (Sat, 19 Jul 2008) $
 * $Rev: 1306 $
 * $Author: styck $
 * $Id: functions.php 1306 2008-07-19 06:33:18Z styck $
 */

if (!defined('ORIGINAL_PAGE')) die('This file cannot be accessed directly.');


$pathf = realpath(dirname(__FILE__));


require_once "$pathf/defines.php";
require_once "$pathf/error_codes.php";
require_once "$pathf/urdversion.php";
require_once "$pathf/autoincludes.php";
require_once "$pathf/defaults.php";
require_once "$pathf/libs/magpierss/rss_fetch.inc";


function my_escapeshellcmd($str,  $hide_space=TRUE) 
{ // alias
	return my_escapeshellarg($str);
}

function my_escapeshellarg($str, $hide_space=TRUE)
{ // you really have to do everything yourself in php...
	// note that this isn't a proper command line escape thingie as it doesn't handle piping and quoting by it self. It just escapes all the naughty characters.
	$chars = array ('\\', '#', '&', ';', '`', '|', '*', '?', '~', '<', '>', '^', '(', ')', '[', ']', '{', '}', '$', '\x0A', '\xFF', '\'', '"');
	if ($hide_space)
		$chars[] = ' ';
	foreach ($chars as $c) 
		$str = str_replace($c, '\\' . $c, $str);
	
	return  $str ;
}


function is_autodownloader(DatabaseConnection $db, $userID)
{
	assert(is_numeric($userID));

	$seteditorrights = '%A%';
	$db->escape($userID, TRUE);
	$db->escape($seteditorrights, TRUE);

	$qry = "\"rights\" FROM users WHERE \"ID\" = $userID AND \"rights\" LIKE $seteditorrights";
	$rv = $db->select_query($qry);
	if ($rv === FALSE)
		return FALSE;

	return TRUE;
}


function is_seteditor(DatabaseConnection $db, $userID)
{
	assert(is_numeric($userID));

	$seteditorrights = '%C%';
	$db->escape($userID, TRUE);
	$db->escape($seteditorrights, TRUE);

	$qry = "\"rights\" FROM users WHERE \"ID\" = $userID AND \"rights\" LIKE $seteditorrights";
	$rv = $db->select_query($qry);
	if ($rv === FALSE)
		return FALSE;

	return TRUE;
}


function is_poster(DatabaseConnection $db, $userID)
{
	assert(is_numeric($userID));

	$seteditorrights = '%P%';
	$db->escape($userID, TRUE);
    $db->escape($seteditorrights, TRUE);
    $like = $db->get_pattern_search_command('LIKE');


	$qry = "\"rights\" FROM users WHERE \"ID\" = $userID AND \"rights\" $like $seteditorrights";
	$rv = $db->select_query($qry);
	if ($rv === FALSE)
		return FALSE;

	return TRUE;
}



function is_admin(DatabaseConnection $db, $username)
{
	if (is_numeric($username))
		$qry = "\"isadmin\" FROM users WHERE \"ID\" = $username";
	else {
		$db->escape($username, TRUE);
		$qry = "\"isadmin\" FROM users WHERE \"name\" = $username";
	}
	$rv = $db->select_query($qry);
	if ($rv === FALSE)
		return FALSE;
	if ($rv[0]['isadmin'] >= 1)
		return TRUE;

	return FALSE;
}


function valid_username($username, $may_exist=0)
{ // may_exist: 0 -- don't care, 1 must exist, 2 must not exist
    if ($may_exist != 0) {
        global $db;
        $db_username = $username;
        $db->escape($db_username, TRUE);
        $res = $db->select_query("\"ID\" FROM users WHERE \"name\" = $db_username");
        if ($res !== FALSE && $may_exist == 2) 
            throw new exception('User already exists',ERR_INVALID_USERNAME); 
        else if ($res === FALSE && $may_exist == 1) 
            throw new exception('User does not exists',ERR_INVALID_USERNAME); 
    }
	if (is_numeric($username))
        return FALSE;
	return ((preg_match('/^([A-Za-z_0-9]){3,}$/', $username) === 1) && (strtolower($username) != 'root'));
}


function format_size($value, $format, $suffix='B', $base=1024)
{
	assert(is_numeric($value));
	switch(strtolower($format)) {
	default:
	case 'b':
		$t = '';
		break;
	case 'kb':
	case 'k':
		$t = 'k';
		$value /= ($base);
		break;
	case 'm':
	case 'mb':
		$t = 'M';
		$value /= ($base*$base);
		break;
	case 'g':
	case 'gb': 
		$t = 'G';
		$value /= ($base*$base*$base);
		break;
	case 't':
	case 'tb': 
		$t = 'T';
		$value /= ($base*$base*$base*$base);
		break;
	case 'h':
		if ($value < $base) {
			$t = ''; 
		} elseif ($value < ($base*$base)) {
			$value /= $base;
			$t = 'k';
		} elseif ($value < ($base*$base*$base)) {
			$value /= ($base*$base);
			$t = 'M';
		} elseif ($value < ($base*$base*$base*$base)) {
			$value /= ($base*$base*$base);
			$t = 'G';
		} else {
			$value /= ($base*$base*$base*$base);
			$t = 'T';
		}
		break;
	}
	$value = round($value, 1);
	return array($value, $t . $suffix);
}


function readable_size($bytes, $dec=1, $unit = 'B') 
{
	assert(is_numeric($bytes) && is_numeric($dec));

	$decsep = '.';
	// Language specific settings:
	global $LN;
	if (isset($LN) && isset($LN['decimalseparator']))
		$decsep = $LN['decimalseparator'];

	if($bytes <= 0) 
		return '0';
	elseif($bytes >= 1099511627776) {
		$return = round($bytes / 1024 / 1024 / 1024 / 1024, $dec);
		$suffix = 'T';
	} elseif($bytes >= 1073741824) {
		$return = round($bytes / 1024 / 1024 / 1024, $dec);
		$suffix = 'G';
	} elseif($bytes >= 1048576) {
		$return = round($bytes / 1024 / 1024, $dec);
		$suffix = 'M';
	} elseif($bytes >= 1024) {
		$return = round($bytes / 1024, $dec);
		$suffix = 'k';
	} else {
		$return = $bytes;
		$suffix = '';
	}
	$return = number_format($return, $dec, $decsep, '');
	$return .= ' ' . $suffix . $unit;
	return $return;
}


function get_microtime() 
{
	list($usec, $sec) = explode(" ", microtime());
	return ((float)$usec + (float)$sec);
}


function sec2min($diff) 
{
	assert(is_numeric($diff));
	$secsDiff = $diff % 60;
	$minsDiff = floor ($diff / 60);
	return ($minsDiff.'m '.$secsDiff.'s');
}


function get_download_size(DatabaseConnection $db, $dlid)
{
    $db->escape($dlid, TRUE);
    $sql = "SELECT \"size\" FROM downloadinfo WHERE \"ID\"=$dlid";
    $res = $db->execute_query($sql);
    if (!isset($res[0]['size']))
        throw new exception("Download ID not found $dlid");
    return $res[0]['size'];
}


function readable_time($timediff, $value = 'specific')
{
	assert(is_numeric($timediff));
	global $LN;
    if (!isset($LN)) 
        return 'NOLANGUAGE';

	if ($timediff < 0) 
		return '?';
	switch ($value) {
	default:
        assert(FALSE);
	case 'largest_long':
		$years = (int) ($timediff / (3600 * 24 * 365));
		$timediff -= ($years * 3600 * 24 * 365);
		$weeks = (int) ($timediff / (3600 * 24 * 7));
		$timediff -= ($weeks * 3600 * 24 * 7);
		$days = (int) ($timediff / (3600 * 24));
		$timediff -= ($days * 3600 * 24);
		$hours = (int) ($timediff / 3600);
		$timediff -= ($hours * 3600);
		$minutes = (int) ($timediff / 60);
		$timediff -= ($minutes * 60);
		$seconds = $timediff;
		$i = 0; 
		$return = '';
		if ($years > 0) { return $years.' ' . $LN['year' . ($years == 1? '':'s')];}
		if ($weeks > 0) { return $weeks.' ' . $LN['week' . ($weeks == 1? '':'s')];}
		if ($days > 0) { return $days.' ' . $LN['day' . ($days == 1? '':'s')];}
		if ($hours > 0) { return $hours.' ' . $LN['hour' . ($hours == 1? '':'s')];}
		if ($minutes > 0) { return $minutes.' ' . $LN['minute' . ($minutes == 1? '':'s')];}
		return $seconds. ' ' . $LN['second' . ($seconds == 1? '':'s')] ;
		break;

	case 'largest_two':
		$years = (int) ($timediff / (3600 * 24 * 365));
		$timediff -= ($years * 3600 * 24 * 365);
		$weeks = (int) ($timediff / (3600 * 24 * 7));
		$timediff -= ($weeks * 3600 * 24 * 7);
		$days = (int) ($timediff / (3600 * 24));
		$timediff -= ($days * 3600 * 24);
		$hours = (int) ($timediff / 3600);
		$timediff -= ($hours * 3600);
		$minutes = (int) ($timediff / 60);
		$timediff -= ($minutes * 60);
		$seconds = $timediff;
		$i = 0; 
		$return = '';
		if ($years > 0) { return  $years.' ' . $LN['year_short'];}
		if ($weeks > 0) { return $weeks.' ' . $LN['week_short'];}
		if ($days > 0) { return $days.' ' . $LN['day_short'];}
		if ($hours > 0) { return $hours.' ' . $LN['hour_short'];}
		if ($minutes > 0) { return $minutes.' ' . $LN['minute_short'];}
		return $seconds . ' ' .$LN['second_short'] ;
		break;
	case 'specific':
		$years = (int) ($timediff / (3600 * 24 * 365));
		$timediff -= ($years * 3600 * 24 * 365);
		$weeks = (int) ($timediff / (3600 * 24 * 7));
		$timediff -= ($weeks * 3600 * 24 * 7);
		$days = (int) ($timediff / (3600 * 24));
		$timediff -= ($days * 3600 * 24);
		$hours = (int) ($timediff / 3600);
		$timediff -= ($hours * 3600);
		$minutes = (int) ($timediff / 60);
		$timediff -= ($minutes * 60);
		$seconds = $timediff;

		$return = '';
		if ($years > 0) $return .= $years. $LN['year_short'] . ' ';
		if ($weeks > 0) $return .= $weeks. $LN['week_short'] . ' ';
		if ($days > 0) $return .= $days. $LN['day_short'] . ' ';
		if ($hours > 0) $return .= $hours. $LN['hour_short'] . ' ';
        if ($minutes > 0) $return .= $minutes. $LN['minute_short'] . ' ';
        if ($return == '' || $seconds > 0)
            $return .= $seconds. $LN['second_short'];
		break;
	case 'hours':
		$hr = (int)($timediff / 3600);
		$hrstr = floor($hr);
		$mn = (int) ($timediff % 60);
		$mnstr = floor($mn);
		$return = "$hr" . $LN['hour_short'] . ":$mn" . $LN['minute_short'];
		break;
	case 'minutes':
		$val = $timediff / 60;
		$return = round($val, 1) . $LN['minute_short'];
		break;
	case 'fancy':
		if ($timediff < 60) {
			$return = '0:';
			if ($timediff < 10) $return .= '0';
			$return .= $timediff;
		} else {
			$days = (int) ($timediff / (3600 * 24));
			$timediff -= ($days * 3600 * 24);
			$hours = (int) ($timediff / 3600);
			$timediff -= ($hours * 3600);
			$minutes = (int) ($timediff / 60);
			$timediff -= ($minutes * 60);
			$seconds = $timediff;
			if ($minutes < 10) $minutes = '0'.$minutes;
			if ($seconds < 10) $seconds = '0'.$seconds;
			$return = "$hours:$minutes:$seconds";
			$return = ltrim($return, ':0 ');
			if ($days > 0)
				$return = $days . ' ' . $LN['day_short'] . ' ' . $return;
		}
		break;
	}
	return $return;
}


function time_format($time)
{
	assert(is_numeric($time));

	$dateformat = 'Y-m-d';
	$timeformat = 'H:i:s';

	// Language specific settings:
	global $LN;
	if (isset($LN) && isset($LN['dateformat']) && isset($LN['timeformat'])) {
		$dateformat = $LN['dateformat'];
		$timeformat = $LN['timeformat'];
	}

	if ($time == 0)
		return '';
	$now = time() + 60; // 1 min offset...
	$dn = getdate($now);
	$dt = getdate($time);
	if ($dn['mday'] == $dt['mday'] &&
		$dn['mon'] == $dt['mon'] &&
		$dn['year'] == $dt['year'])
		return date($timeformat, $time);
	else
		return date($dateformat . ' ' . $timeformat, $time);
}


function load_config(DatabaseConnection $db)
{
	$res = $db->select_query("* FROM preferences WHERE \"userID\" = '0'");
	if (!is_array($res)) 
		$res = array();

	$prefs = array();

	foreach ($res as $row) 
		$prefs[$row['option']] = $row['value'];

	return $prefs;
}


function load_prefs_clean(DatabaseConnection $db, $uid)
{
	assert(is_numeric($uid));
	$db->escape($uid, TRUE);
	
	$res = $db->select_query("* FROM preferences WHERE \"userID\" = $uid");
	if (!is_array($res)) 
		$res = array();

	$prefs = array();

	foreach ($res as $row) 
		$prefs[$row['option']] = $row['value'];

	return $prefs;
}


function delete_files($files, $dir)
{
	foreach ($files as $f) {
		$sep =  (substr($dir, -1) == '/') ? '' : '/'; // do we need an extra / ?
		$fn = $dir . $sep . $f;
		$res = unlink ($fn);
		if ($res === FALSE)
			write_log("Could not delete '$fn'", LOG_NOTICE);
		else 
			echo_debug("Deleted file: '$fn'", DEBUG_WORKER);
	}
}


function get_config_default(DatabaseConnection $db, $var, $default='')
{
	try {
		return get_config($db, $var);
	}catch (exception $e) {
		return $default;
	}
}


function get_config(DatabaseConnection $db, $name)
{
	global $LN;
	assert ($name != '');
	$db->escape($name, TRUE);
	$query = " \"value\" FROM preferences WHERE \"userID\"=0 AND \"option\"=$name";
	$r = $db->select_query($query);
	if ($r === FALSE) 
		throw new exception ($LN['error_prefnotfound']. '(' . $name . ')', ERR_INVALID_OPTION);
	else
		return $r[0]['value'];

}

function get_pref(DatabaseConnection $db, $name, $username, $default=NULL)
{
	global $LN;
	assert ($name != '');
	$db->escape($name, TRUE);
	if (!is_numeric($username)) {
		$db->escape($username, TRUE);
		$query = "\"value\" FROM preferences, users WHERE users.\"ID\" = preferences.\"userID\" AND users.\"name\" = $username AND preferences.\"option\"=$name";
	} else 
		$query = "\"value\" FROM preferences WHERE \"userID\"= $username AND \"option\"=$name";
	$r = $db->select_query($query);
	if ($r === FALSE) {
		if ($default === NULL)
			throw new exception ($LN['error_prefnotfound']. '(' . $name . ')', ERR_INVALID_OPTION);
		else
			return $default;
	} else
		return $r[0]['value'];
}


function set_prefs(DatabaseConnection $db,$userid, array $settings)
{
	foreach ($settings as $n=>$v)
		set_pref($db, $n, $v, $userid);
}

function set_configs(DatabaseConnection $db, array $settings)
{
	foreach ($settings as $n=>$v)
		set_config($db, $n, $v);
}

function set_pref(DatabaseConnection $db, $name, $value, $userid)
{
	assert ($name != '' && is_numeric($userid));
	$dbname = $name;
	$db->escape($dbname, TRUE);
	$dbuserid = $userid;
	$db->escape($dbuserid, TRUE);
	$res =  $db->select_query("\"value\", \"userID\" FROM preferences WHERE \"option\" = $dbname AND \"userID\" = $dbuserid");
	if ($res !== FALSE) {
		$db->escape($value, TRUE);
		$res = $db->execute_query("UPDATE \"preferences\" SET \"value\" = $value WHERE \"option\" = $dbname AND \"userID\" = $dbuserid");
	} else 
		$res = $db->insert_query('preferences', array ('value', 'option', 'userID'), array ($value, $name, $userid));
} 


function set_config(DatabaseConnection $db, $name, $value)
{
	assert ($name != '');
	$dbname = $name;
	$db->escape($dbname, TRUE);
	$res = $db->select_query("\"value\" FROM preferences WHERE \"option\" = $dbname AND \"userID\" = 0");
	if ($res !== FALSE) {
		$db->escape($value, TRUE);
		$res = $db->execute_query("UPDATE preferences SET \"value\" = $value WHERE \"option\" = $dbname AND \"userID\" = 0");
	} else 
		$res = $db->insert_query('preferences', array ('value', 'option', 'userID'), array ($value, $name, '0'));
}


function get_dlpath (DatabaseConnection $db)
{
	global $LN;
	$dlpath = get_config($db, 'dlpath');
    if (substr($dlpath, -1, 1) != '/') $dlpath .= '/';
	clearstatcache();
	if (!is_dir($dlpath)) {
		throw new exception ($LN['error_nodlpath'] . " ($dlpath)", ERR_PATH_NOT_FOUND);
	}
	if (!is_writable($dlpath)) {
		throw new exception ($LN['error_dlpathnotwritable'] . " ($dlpath)", ERR_PATH_NOT_WRITABLE);
	}
	// check exists dir otherwise make it
	return $dlpath;
}


function update_queue_priority(DatabaseConnection $db, $id, $priority)
{
	assert (is_numeric($id) && is_numeric($priority));
	$db->escape($priority, TRUE);
	$db->escape($id, TRUE);
	$sql = "UPDATE queueinfo SET \"priority\" = $priority WHERE \"ID\"= $id";
	$res = $db->execute_query($sql);
}


function insert_queue_status (DatabaseConnection $db, $id, $description, $status, $command_id, $username, $paused, $comments=NULL, $priority=1, $restart = TRUE)
{
	assert (is_numeric($id) && is_numeric($priority) && is_bool($restart));
	$time = time();
	$restart = ($restart === TRUE) ? 1 : 0;
	$cols = array ('status', 'description', 'lastupdate', 'starttime', 'paused', 'username', 'progress', 'ETA', 'urdd_id', 'comment', 'priority', 'command_id', 'restart');
	$vals = array ($status, $description, $time, $time, $paused, $username, 0, 0, $id, $comments === NULL? '':$comments, $priority, $command_id, $restart);

	try {
		$db->insert_query ('queueinfo', $cols, $vals);
		$id = $db->get_last_id();
		if ($id === FALSE)
			return FALSE;
		return $id;
	} catch (exception $e) {
		return FALSE;
	}
}


function update_queue_norestart(DatabaseConnection $db, $id)
{
	assert (is_numeric($id));
	$time = time();
	$db->escape($id, TRUE);
	$db->escape($time, TRUE);
	$query = "UPDATE queueinfo SET \"restart\"=0, \"lastupdate\"=$time WHERE \"ID\"=$id";
	$db->execute_query($query);
}


function update_queue_status(DatabaseConnection $db, $id, $status=NULL, $eta=NULL, $progress=NULL, $comments=NULL, $paused=NULL)
{
	echo_debug("updating status $status", DEBUG_MAIN);
	assert (is_numeric($id));
	$time = time();
	$db->escape($time, TRUE);
	$db->escape($id, TRUE);
	$query = "UPDATE queueinfo SET \"lastupdate\"=$time ";
	if ($status !== NULL) {
		$db->escape($status, TRUE);
		$query .= ", \"status\"=$status";
	}
	if ($eta !== NULL) {
		assert (is_numeric($eta));
		$db->escape($eta, TRUE);
		$query .= ", \"ETA\"=$eta";
	}
	if ($progress !== NULL) {
		assert (is_numeric($progress));
		$db->escape($progress, TRUE);
		$query .= ", \"progress\"=$progress";
	}
	if ($comments !== NULL) {
		$db->escape($comments, TRUE);
		$query .= ", \"comment\"=$comments";
	}
	if ($paused !== NULL) {
		assert(is_bool($paused));
		$query .= ", \"paused\"=' ". ($paused?'1':'0') . "'";
	}
	$query .= " WHERE \"ID\"=$id";
	$db->execute_query ($query);
}


function find_name(DatabaseConnection $db, $string)
{
	if (preg_match_all("/\"(.+)\"/U", $string, $vars)) {
		$x = count($vars[1]) - 1;
		if (isset($vars[1][$x])) 
			$filename = $vars[1][$x];
	} else 
		$filename = $string;

    $filename = preg_replace("/(\.r[0-9]{2})|(\.sfv)|(\.nfo)|(\.mp3)|(\.jpg)|(\.nzb)|(\.par2)|(\.7z)|(\.gz)|(\.tar)|(.tgz)|(\.zip)|(.vol[0-9]+-[0-9]+\.PAR2)|(\.part[0-9]+\.rar)/i", '', $filename);
	$filename = trim(sanitise_download_name($db, $filename));

	if (strlen($filename) < 3) 
		$filename .= ' ' . mt_rand(100, 999);
	return $filename;
}


function sanitise_download_name(DatabaseConnection $db, $name)
{
    $replacement_str = get_config($db, 'replacement_str');
    $replacement_str = preg_replace('/[^A-Za-z0-9_\-.();[\] ]/', '', $replacement_str);
	return trim(preg_replace('/[^A-Za-z0-9_\-.();[\]]/', $replacement_str, $name), "_\n\t \r\x00\x0B-;.");
}


function rmdirtree($dirname, $age=0, $delete_top_dir = FALSE) // age in seconds // topdir can go??
{
	assert(is_numeric($age) && is_bool($delete_top_dir));
	global $LN;
	$count = $up_count = 0;
	clearstatcache();
	$error = '';
	if (!is_dir($dirname)) 
		return array (FALSE, '');    //Return false if attempting to operate on a file
	if(!is_writable($dirname))     //Operate on dirs only
		throw new exception ($LN['error_noremovefile2'] . ': ' . $dirname, ERR_PATH_NOT_FOUND);
	if (substr($dirname, -1) != '/') 
		$dirname .= '/';    //Append slash if necessary
	$handle = opendir($dirname);
	while (FALSE !== ($file = readdir($handle))) {
		if ($file == '.' || $file == '..')     // Ignore . and ..
			continue;
		try {
			$path = $dirname.$file;
			if (is_dir($path)) {    //Recurse if subdir, Delete if file
				if (is_writable($path) && is_executable($path)) {
					list ($up_count, $error_in) = rmdirtree($path, $age, FALSE);
					if ($up_count == 0) {
						$rv = @rmdir($path);    //Remove dir
						if ($rv === FALSE)
							$error .= $LN['error_noremovefile2'] . ': ' . $dirname;
					} else 
						$count++;

					$error .= $error_in;
				} else 
					throw new exception ($LN['error_noremovefile2'] . ': ' . $path, ERR_PATH_NOT_FOUND);
			} else {
				$mtime = filemtime($path);
				$now = time();
				if ($age == 0 || (($now - $mtime) > $age)) {
					$rv = @unlink($path);
					if ($rv === FALSE)
						throw new exception ($LN['error_noremovefile'] . ': '. $path, ERR_FILE_NOT_FOUND); 
				} else 
					$count++;
			}
		} catch (exception $e) {
			$error .= $e->getMessage() . ', ';
		}
	}
	closedir($handle);
	if ($delete_top_dir === TRUE)
		rmdir($dirname);
	return array ($count, $error);    
}


function create_root(DatabaseConnection $db)
{
	$db->insert_query('users', array('name', 'pass', 'isadmin', 'active'), array('root', 'nologin', USER_ADMIN, USER_INACTIVE));
	$query = 'UPDATE users SET "ID"=0 WHERE "name"=\'root\''; // force the user ID to 0 doesn't always do that by default due to auto inc
	$res = $db->execute_query($query);
	$prefArray = get_default_config();
	foreach ($prefArray as $var => $val) 
		$db->insert_query('preferences', array ('userID', 'option', 'value'), array ('0', $var, $val));
}


function verify_email($email)
{ // nasty function but hey it works ;-)
	$expr = '[a-z0-9!#$%&\'*+\/=?^_`{|}~\-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~\-]+)*@((?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+(?:[a-z]{2}|com|org|net|gov|mil|biz|int|mobi|name|aero|biz|info|jobs|museum|asia|arpa|cat|coop|edu|pro|tel|travel)|localhost)\b';
	return preg_match("/^$expr$/i", $email) == 1;
}


function clean_fullname($fullname)
{
	$fullname = strip_tags($fullname);
	$fullname = preg_replace('/[^\w- .()\']/', ' ', $fullname);
	return $fullname;
}


function update_user(DatabaseConnection $db, $id, $username, $fullname, $email, $isadmin, $active, $rights)
{
	global $LN;
	assert(is_numeric($id));
	if (verify_email($email) === FALSE)
		throw new exception($LN['error_invalidemail'], ERR_INVALID_EMAIL);
	if (!valid_username($username))
		throw new exception ($LN['error_invalidusername'], ERR_INVALID_USERNAME);
	$fullname = clean_fullname($fullname);

	if (verify_rights($rights) === FALSE)
		throw new exception('Rights are Wrong!', ERR_ACCESS_DENIED);

	$db->escape($username, TRUE);
	$db->escape($fullname, TRUE);
	$db->escape($email, TRUE);
	$db->escape($isadmin, TRUE);
	$db->escape($active, TRUE);
	$db->escape($id, TRUE);
	$db->escape($rights, TRUE);

	$res = $db->select_query("\"ID\" FROM users WHERE \"ID\"=$id", 1);
	if ($res === FALSE) {
		throw new exception ("User not found: $userid");
		return FALSE;
	}
	$query = "UPDATE users SET \"name\"=$username, \"fullname\"=$fullname, \"email\"=$email, \"isadmin\"=$isadmin, \"active\"=$active, \"rights\"=$rights WHERE \"ID\"=$id";
	$res = $db->execute_query($query);
	return TRUE;
}


function check_user(DatabaseConnection $db, $username)
{
	$db->escape($username, TRUE);
	$query = "\"ID\" FROM users WHERE \"name\"=$username";
	$res = $db->select_query($query, 1);
	if (isset($res[0]['ID'])) 
		return TRUE;
	else
		return FALSE;
}


function verify_rights($rights)
{
	if (strspn($rights, RIGHTS_STR) != strlen($rights))
		return FALSE;
	return TRUE;
}


function add_user(DatabaseConnection $db, $username, $fullname, $email, $password, $isadmin, $active, $rights, $plain_password = TRUE, $salt='')
{
    global $LN;
    if ($plain_password === TRUE) {
        $salt = generate_password(8);
        $password = hash('sha256', $salt. $password . $salt);
    }
	$fullname = clean_fullname($fullname);
	$o_username = $username = trim($username);
	if (!valid_username($username))
		throw new exception ($LN['error_invalidusername'], ERR_INVALID_USERNAME);

	if (verify_email($email) === FALSE)
		throw new exception($LN['error_invalidemail']	, ERR_INVALID_EMAIL);
	if (verify_rights($rights) === FALSE)
		throw new exception('Rights are Wrong!', ERR_ACCESS_DENIED);

	$db->escape($username, TRUE);
	$res = $db->select_query("\"ID\" FROM users WHERE \"name\" = $username");
    if ($res !== FALSE) 
        throw new exception('User already exists',ERR_INVALID_USERNAME); 
    
    $time = time();
	$cols = array('name', 'fullname', 'email', 'pass', 'isadmin', 'active', 'regtime', 'rights', 'salt');
	$vals = array($o_username, $fullname, $email, $password, $isadmin, $active, $time, $rights, $salt);
	$db->insert_query('users', $cols, $vals);
	$res = $db->select_query("\"ID\" FROM users WHERE \"name\" = $username");
	if ($res === FALSE)
		return FALSE;
	$userID = $res[0]['ID'];
	$prefArray = get_default_prefs();
	$cols = array('userID', 'option', 'value');
	foreach ($prefArray as $var => $val) {
		$vals = array ($userID, $var, $val); // xxx must be possible to do more efficiently ;-)
		$db->insert_query('preferences', $cols, $vals);
	}
	try {
		$path = get_dlpath($db);
		create_user_dirs($db, $path, $o_username);
	} catch (exception $e) {
		// dlpath not (yet) set ... in installer
	}
	return TRUE;
}


function get_usenet_server(DatabaseConnection $db, $id, $active = TRUE)
{
	assert(is_bool($active) && is_numeric($id));
	$db->escape($id, TRUE);
	if ($active === TRUE)
		$prio_sql = ' AND "priority" > 0';
	else 
		$prio_sql = '';
	$sql = "* FROM usenet_servers WHERE \"id\" = $id $prio_sql";
	$res = $db->select_query($sql, 1);
	if ($res === FALSE)
		throw new exception ('No active usenet server selected', ERR_NO_ACTIVE_SERVER);
	$srv['id'] = $res[0]['id'];
	$srv['name'] = $res[0]['name'];
	$srv['hostname'] = $res[0]['hostname'];
	$srv['threads'] = $res[0]['threads'];
	$srv['connection'] = $res[0]['connection'];
	$srv['port'] = ($res[0]['connection'] == 'off') ?  $res[0]['port'] : $res[0]['secure_port'];
	$srv['authentication'] = (bool) $res[0]['authentication'];
    $srv['retention'] = $res[0]['retention'];
    $srv['posting'] = $res[0]['posting'];
    $srv['compressed_headers'] = $res[0]['compressed_headers'];
    $srv['priority'] = $res[0]['priority'];
	if ($srv['authentication'] != 0) {
		$srv['username'] = $res[0]['username'];
		$srv['password'] = $res[0]['password'];
	} else {
		$srv['username'] = '';
		$srv['password'] = '';
	}
	return $srv;
}


function get_all_usenet_servers(DatabaseConnection $db, $active=TRUE)
{ // todo fix get update server too
	assert(is_bool($active));
	if ($active === TRUE)
		$prio_sql = ' WHERE "priority" > 0 ';
	else 
		$prio_sql = '';
	$sql = "* FROM usenet_servers $prio_sql ORDER BY \"name\" ASC";
	$res = $db->select_query($sql);
	if ($res === FALSE)
		throw new exception ('No active usenet servers found', ERR_NO_ACTIVE_SERVER);
	foreach ($res as $row) {
		$srv['id'] = $row['id'];
		$srv['name'] = $row['name'];
		$srv['hostname'] = $row['hostname'];
		$srv['threads'] = $row['threads'];
		$srv['connection'] = $row['connection'];
        $srv['port'] = ($row['connection'] == 'off') ?  $row['port'] : $row['secure_port'];
        $srv['plain_port'] = $row['port'];
        $srv['secure_port'] = $row['secure_port'];
		$srv['authentication'] = $row['authentication'];
		$srv['priority'] = $row['priority'];
		$srv['retention'] = $row['retention'];
		$srv['posting'] = $row['posting'];
		$srv['compressed_headers'] = $row['compressed_headers'];
		if ($srv['authentication'] != 0) {
			$srv['username'] = $row['username'];
			$srv['password'] = $row['password'];
		} else {
			$srv['username'] = '';
			$srv['password'] = '';
		}
		$servers[] = $srv;
	}
	return $servers;
}


function create_usenet_servers(DatabaseConnection $db)
{
	add_usenet_server($db, '4usenet', 'news.4usenet.nl', 119, 563, 6, 'ssl', 1, 'XXXXXXX', 'XXX', 0, 0, 0, 0);
	add_usenet_server($db, 'Astraweb', 'news.astraweb.com', 119, 563, 20, 'off', 1, 'XXXXXXX', 'XXX', 0, 0, 0, 1);
	add_usenet_server($db, 'Download 2 Day', 'reader.download2day.nl', 119, 563, 6, 'off', 1, 'XXXXXXX', 'XXX', 0, 0, 0, 0);
	add_usenet_server($db, 'Easynews', 'news.easynews.com', 119, 563, 4, 'off', 1, 'XXXXXXX', 'XXX', 0, 0, 0, 0);
	add_usenet_server($db, 'Ewaka IPv6', 'news.ipv6.eweka.nl', 119, 0, 8, 'off', 0, '', '', 0, 0, 0, 0);
	add_usenet_server($db, 'Giganews (America)', 'news.giganews.com', 119, 563, 10, 'off', 1, 'XXXXXXX', 'XXX', 0, 0, 0, 1);
	add_usenet_server($db, 'Giganews (Europe)', 'news-europe.giganews.com', 119, 563, 10, 'off', 1, 'XXXXXXX', 'XXX', 0, 0, 0, 1);
	add_usenet_server($db, 'Hitnews', 'news.hitnews.eu', 119, 563, 4, 'off', 0, '', '', 0, 0, 0, 0);
	add_usenet_server($db, 'Internet Solutions', 'news.is.co.za', 119, 563, 4, 'off', 0, '', '', 0, 0, 0, 0);
	add_usenet_server($db, 'ITgate', 'news.itgate.net', 119, 563, 4, 'off', 0, '', '', 0, 0, 0, 0);
	add_usenet_server($db, 'MWeb', 'news.mweb.co.za', 119, 563, 4, 'off', 0, '', '',  0, 0, 0, 0);
	add_usenet_server($db, 'Ngroups', 'news.us.ngroups.net', 119, 0, 3, 'off', 1, 'XXX', 'XXX', 0, 0, 0, 0);
	add_usenet_server($db, 'Saix', 'news.saix.net', 119, 0, 3, 'off', 1, '', '', 0, 0, 0, 0);
	add_usenet_server($db, 'Web Africa', 'news.webafrica.co.za', 119, 0, 3, 'off', 1, '', '', 0, 0, 0, 0);
	add_usenet_server($db, 'XS4all - Newszilla', 'newszilla.xs4all.nl', 119, 0, 4, 'off', 0, '', '', 0, 0, 0, 1);
	add_usenet_server($db, 'XS4all - Newszilla IPv6', 'newszilla6.xs4all.nl', 119, 0, 4, 'off', 0, '', '', 0, 0, 0, 1);
	add_usenet_server($db, 'XSnews (IPv6)', 'reader.ipv6.xsnews.nl', 119, 563, 10, 'off', 0, '', '',  0, 0, 0, 0);
	add_usenet_server($db, 'Yabnews', 'news.yabnews.com', 119, 0, 4, 'off', 1, 'ynXXXXXXX', 'XXX', 0, 0, 0, 0);
}


function add_usenet_server(DatabaseConnection $db, $name, $hostname, $port, $secure_port, $threads, $connection, $authentication, $username, $password, $priority=DEFAULT_USENET_SERVER_PRIORITY, $retention=0, $compressed_headers=FALSE, $posting=FALSE)
{
	assert(is_numeric($priority) && is_numeric($port) && is_numeric($secure_port) && is_numeric($threads));
	global $LN;
	$name = trim($name);
	$hostname = trim($hostname);
	$username = trim($username);
	$password = trim($password);
    $connection = trim($connection);
    
	$cols = array('name', 'hostname', 'port', 'secure_port', 'threads', 'connection', 'authentication', 'username', 'password', 'priority', 'retention', 'compressed_headers', 'posting');
	$vals = array($name, $hostname, $port, $secure_port, $threads, $connection, ($authentication?1:0), $username, $password, $priority, $retention, ($compressed_headers?1:0), ($posting?1:0));
	$db->insert_query('usenet_servers', $cols, $vals);

	$db->escape($name, TRUE);
	$res = $db->select_query("\"id\" FROM usenet_servers WHERE \"name\" = $name");
	if ($res === FALSE)
		return FALSE;
	return $db->get_last_id(); 
}


function set_posting(DatabaseConnection $db, $id,$posting)
{
    assert(is_numeric($id));
    $posting = ($posting ?1:0);
    $db->escape($posting, TRUE);
    $db->escape($id, TRUE);
    $sql = "UPDATE usenet_servers SET \"posting\"=$posting WHERE \"id\"=$id";
    $db->execute_query($sql);
}


function smart_update_usenet_server(DatabaseConnection $db, $id, $name=NULL, $hostname=NULL, $port=NULL, $secure_port=NULL, $threads=NULL, $connection=NULL, $authentication=NULL, $username=NULL, $password=NULL, $priority=NULL, $retention=NULL, $compressed_headers=NULL, $posting= NULL)
{
    assert(is_numeric($id));
    $sql = "UPDATE usenet_servers SET ";
    if ($name !== NULL) {
        $name = trim($name);
        $db->escape($name, TRUE);
        $sql .=  "\"name\"=$name, ";
    }

    if ($hostname !== NULL) {
        $hostname = trim($hostname);
        $db->escape($hostname, TRUE);
        $sql .=  "\"hostname\"=$hostname, ";
    }

    if ($username !== NULL) {
        $username = trim($username);
        $db->escape($username, TRUE);
        $sql .=  "\"username\"=$username, ";
    }
    if ($password !== NULL) {
        $password = trim($password);
        $db->escape($password, TRUE);
        $sql .= "\"password\"=$password, ";
    }
    if ($connection !== NULL) { 
        $connection = trim($connection);
        $db->escape($connection, TRUE);
        $sql .= "\"connection\"=$connection, ";

    }
    if ($port !== NULL) {
        assert(is_numeric($port));
        $db->escape($port, TRUE);
        $sql .= "\"port\"=$port, ";
    }
    if ($secure_port !== NULL) {
        assert(is_numeric($secure_port));
        $db->escape($secure_port, TRUE);
        $sql .= "\"secure_port\"=$secure_port, ";
    }
    if ($retention !== NULL) {
        assert(is_numeric($retention));
        $db->escape($retention, TRUE);
        $sql .= "\"retention\"=$retention, ";
    }
    if ($compressed_headers !== NULL) {
        assert(is_bool($compressed_headers));
        $compressed_headers = ($compressed_headers ? 1:0);
        $sql .= "\"compressed_headers\"=$compressed_headers, ";
    }
    if ($threads !== NULL) {
        assert(is_numeric($threads));
        $db->escape($threads, TRUE);
        $sql .= "\"threads\"=$threads, ";
    }

    if ($authentication !== NULL) {
        $db->escape($authentication, TRUE);
        $sql .= "\"authentication\"=$authentication, ";
    }
    if ($posting !== NULL) {
        $posting = ($posting ?1:0);
        $db->escape($posting, TRUE);
        $sql .= "\"posting\"=$posting, ";
    }
    if ($priority !== NULL) {
        assert(is_numeric($priority) );
        $db->escape($priority, TRUE);
        $sql .= "\"priority\"=$priority, ";
    }
    $sql = rtrim($sql, ', ');
    $db->escape($id, TRUE);
    $sql .= " WHERE \"id\"=$id";

    $db->execute_query($sql);
    return TRUE;
}


function update_usenet_server(DatabaseConnection $db, $id, $name, $hostname, $port, $secure_port, $threads, $connection, $authentication, $username, $password, $priority, $retention, $compressed_headers, $posting)
{
	assert(is_numeric($priority) && is_numeric($port) && is_numeric($secure_port) && is_numeric($threads) && is_numeric($id));
	$name = trim($name);
	$hostname = trim($hostname);
	$username = trim($username);
	$password = trim($password);
    $connection = trim($connection);
    $authentication =  $authentication?1:0;
    $compressed_headers =  $compressed_headers?1:0;
    $posting = ($posting?1:0);
	$db->escape($name, TRUE);
	$db->escape($id, TRUE);
	$db->escape($hostname, TRUE);
	$db->escape($port, TRUE);
	$db->escape($secure_port, TRUE);
	$db->escape($threads, TRUE);
	$db->escape($connection, TRUE);
	$db->escape($authentication, TRUE);
	$db->escape($username, TRUE);
	$db->escape($password, TRUE);
	$db->escape($priority, TRUE);
	$db->escape($posting, TRUE);
	$db->escape($retention, TRUE);
	$db->escape($compressed_headers, TRUE);

	$sql = "UPDATE usenet_servers SET \"name\"=$name, \"hostname\"=$hostname, \"port\"=$port, \"secure_port\"= $secure_port, \"threads\"=$threads, ";
    $sql .= "\"connection\"= $connection, \"authentication\"=$authentication, \"username\"=$username, \"password\"=$password, \"retention\"=$retention, ";
    $sql .= "\"compressed_headers\"=$compressed_headers, \"priority\"=$priority, \"posting\"=$posting WHERE \"id\"=$id";
	$db->execute_query($sql);
	return TRUE;
}


function disable_usenet_server(DatabaseConnection $db, $id)
{	
	assert(is_numeric($id));
	set_priority_usenet_server($db, $id, DISABLED_USENET_SERVER_PRIORITY);
}


function enable_usenet_server(DatabaseConnection $db, $id, $priority=0)
{	
	assert(is_numeric($id) && is_numeric($priority));
	if ($priority <= 0 ||$priority > 100)
		$priority = DEFAULT_USENET_SERVER_PRIORITY;
	set_priority_usenet_server($db, $id, $priority);
}


function set_priority_usenet_server(DatabaseConnection $db, $id, $priority)
{
	assert(is_numeric($id) && is_numeric($priority));
	$db->escape($id, TRUE);
	$db->escape($priority, TRUE);
	$sql = "UPDATE usenet_servers SET \"priority\"= $priority WHERE \"id\"=$id";
	$db->execute_query($sql);
}	


function delete_usenet_server(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$update_id = get_config($db, 'preferred_server');
	if ($update_id == $id) // if we delete the preferred server, we reset the preferred server 
		set_config($db, 'preferred_server', 0);
	$db->escape($id, TRUE);
	$sql = "DELETE FROM usenet_servers WHERE \"id\"=$id";
	$db->execute_query($sql);
}


function delete_user(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	if ($id == 0)
		fatal_error($LN['error_nodeleteroot']);

	$db->escape($id, TRUE);
	$query = "DELETE FROM users WHERE \"ID\"=$id"; // delete the user
	$db->execute_query($query);
	$query = "DELETE FROM preferences WHERE \"userID\"=$id"; // delete the settings of the user
	$db->execute_query($query);
	$query = "DELETE FROM usergroupinfo WHERE \"userid\"=$id"; // delete the newsgroup settings of the user
	$db->execute_query($query);
}


function connect_db()
{
	global $pathf;
	require("$pathf/../dbconfig.php");
	$db = new DatabaseConnection($config['databasetype'], $config['db_hostname'], $config['db_user'], $config['db_password'], $config['database'], TRUE);

	// If mysql, set UTF-8 as character set:
	if ($config['databasetype'] == 'mysql') 
		$db->execute_query('SET CHARACTER SET UTF8');
	return $db;
}


function store_ETA(DatabaseConnection $db, $eta, $percentage, $speed, $dbid)
{
	assert(is_numeric($eta) && is_numeric($percentage) && is_numeric($dbid));
    $time = time();
    $eta = floor($eta);
	$db->escape($eta, FALSE);
	$db->escape($percentage, TRUE);
	$db->escape($speed, TRUE);
    $db->escape($dbid, TRUE);
    $db->escape($time, TRUE);
    $qry = "UPDATE queueinfo SET \"ETA\"=(($eta + \"ETA\") / 2), \"progress\"=$percentage, \"comment\"=$speed, \"lastupdate\"=$time WHERE \"ID\"=$dbid";
	$db->execute_query($qry);
}


function add_schedule(DatabaseConnection $db, $cmd, $stime, $repeat, $username)
{
	assert(is_numeric($stime));
	if ($repeat === NULL) $repeat = 0;
	assert(is_numeric($repeat));
	$db->insert_query('schedule', array ('command', 'at_time', 'interval', 'username'), array ($cmd, $stime, $repeat, $username));
	return $db->get_last_id();
}


function set_period_rss(urdd_client $uc, DatabaseConnection $db, $id, $first_update, $period, $time, $periodselect)
{
	assert(is_numeric($time) && is_numeric($periodselect) && is_numeric($period) && is_numeric($id));
	// $time = update-time that's displayed in the newsgroup page
	$db->escape($time, TRUE);
	$db->escape($periodselect, TRUE);
	$dbid = $id;
	$db->escape($dbid, TRUE);

	$sql = "UPDATE rss_urls SET \"refresh_time\"=$time, \"refresh_period\"=$periodselect WHERE \"id\"=$dbid";
	$db->execute_query($sql);
	$resp = $uc->schedule(COMMAND_UPDATE_RSS, $id, $first_update, $period * 3600);	
}


function set_period(urdd_client $uc, DatabaseConnection $db, $id, $first_update, $period, $time, $periodselect)
{
    assert(is_numeric($time) && is_numeric($periodselect) && is_numeric($period) && is_numeric($id));
    // $time = update-time that's displayed in the newsgroup page
//    var_dump($time , $periodselect, $period, $id);
	$db->escape($time, TRUE);
	$db->escape($periodselect, TRUE);
	$dbid = $id;
	$db->escape($dbid, TRUE);

	$sql = "UPDATE groups SET \"refresh_time\"=$time, \"refresh_period\"=$periodselect WHERE \"ID\"=$dbid";
	$db->execute_query($sql);
	$resp = $uc->schedule(COMMAND_UPDATE, $id, $first_update, $period * 3600);	
}


function delete_schedule (DatabaseConnection$db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$db->execute_query("DELETE FROM schedule where \"id\" = $id");
}


function shorten_newsgroup_name($ng, $maxsize=35)
{
	assert(is_numeric($maxsize) && is_string($ng));
	$short = str_ireplace('alt.', 'a.', $ng);
	$short = str_ireplace('binaries.', 'b.', $short);
	if (strlen($short) > $maxsize && $maxsize > 3) {		
		$short = substr($short, 0, $maxsize - 3);
		$short .= '...';
	}
	return $short;
}


function generate_password ($length = MIN_PASSWORD_LENGTH)
{
	assert(is_numeric($length));
	$password = '';
	$possible = '0123456789abcdefghijkmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; 
	$p_len = strlen($possible)-1;
	for ($i = 0; $i < $length; $i++) { 
		$char = $possible[ mt_rand(0, $p_len)];
		$password .= $char;
	}
	return $password;
}


function verify_password($password, $username = NULL)
{
	global $LN;
	$len = strlen($password);
	if ($len < MIN_PASSWORD_LENGTH)
		return $LN['error_pwlength'];
	if ($username != NULL) {
		$slen = levenshtein($password, $username); // must not be too much like the username
		if ($slen < 6) 
			return $LN['error_pwusername'];
	}

	$ucc = preg_match_all('/[A-Z]/', $password, $foo);
	$lcc = preg_match_all('/[a-z]/', $password, $foo);
	$ncc = preg_match_all('/[0-9]/', $password, $foo);
	$oc = preg_match_all('/[^0-9a-zA-Z]/', $password, $foo);

	if ( ($ucc == $len || $lcc == $len || $ncc == $len || $oc == $len) ) // must contain characters from more than one class
		return $LN['error_pwsimple'];
	return TRUE;
}


function get_set_size($name)
{
	// Detect max setsize from the subject name:
	// First check for [001/103]:
	if (preg_match("/\[([0-9]+)[\/]+([0-9]+)\]/", $name, $vars))
		return $vars[2];

	// Ok so that didn't work, less strict: (allows "(001-103)")
	if (preg_match("/[\[(]([0-9]+)[\/of-]+([0-9]+)[\])]/", $name, $vars))
		return $vars[2];

	// "Catch all": (no ( or ['s required, just 2 numbers with '/','\','of' or '-' in between)
	if (preg_match("/([0-9]+)[\/of\- ]+([0-9]+)/", $name, $vars))
		return $vars[2];

	return 0;
}


/* read last X lines of a given text file
   $filename = full path + filename (i.e. /home/marco/file.txt)
   $lines       = number of lines (i.e. 10)
 */
function read_last_lines($filename, $maxlines, &$error, $match, $min_log_level)
{
	global $log_str;
	assert(is_numeric($maxlines));
	/* freely customisable number of lines read per time*/
	$bufferlength = 5000;

	$handle = @fopen($filename, 'r');
	if (!$handle) {
		$error = URD_FILENOTFOUND; 
		return -1;
	}

	/*get the file size with a trick*/
	fseek($handle, 0, SEEK_END);
	$filesize = ftell($handle);

	/*don't want to get past the start-of-file*/
	$position= - min($bufferlength, $filesize);
	$aliq = '';
	$lines = array();

	while ($maxlines > 0) {
		if ($err = fseek($handle, $position, SEEK_END)) {  /* should not happen but it's better if we check it*/
			$error = URD_SEEKERROR;
			fclose($handle);
			return $lines;
		}

		/* big read*/
		$buffer = fread($handle, $bufferlength);

		/* small split*/
		$tmp2 = explode("\n", $buffer);

		/*previous read could have stored a partial line in $aliq*/
		if ($aliq != '') {
			/*concatenate current last line with the piece left from the previous read*/
			$cnt = count($tmp2); 
			if ($cnt > 0)
				$tmp2[$cnt-1] .= $aliq;
			else 
				$tmp2[] = $aliq;
		}

		/*drop first line because it may not be complete*/
		$aliq = array_shift($tmp2);

		$tmp = array();
		$read = 0;
		foreach($tmp2 as $line) {
			if ($match != '' && stripos($line, $match) === FALSE) continue;
			if ($min_log_level !== FALSE) {

                $t = explode (' ', $line);
				if (!isset($t[5])) continue;
				$log_level = array_search($t[5], $log_str);
				if ($log_level === FALSE) continue;
				if ($log_level > $min_log_level) continue;
			}
			$tmp[] = $line;
			$read ++;
		}
		if ( $read  >= $maxlines ) {   /*have read too much!*/
			$tmp2 = array_slice($tmp, $read-$maxlines);
			/* merge it with the array which will be returned by the function*/
			$lines = array_merge($tmp2, $lines);

			/* break the cycle*/
			$maxlines = 0;
		} elseif (-$position >= $filesize) {  /* haven't read enough but arrived at the start of file*/

			//get back $aliq which contains the very first line of the file
			if (!is_array($aliq)) $aliq = array();
			if (!is_array($tmp)) $tmp = array();
			$lines = array_merge($aliq, $tmp, $lines);

			//force it to stop reading
			$maxlines = 0;

		} else {              /*continue reading...*/

			//add the freshly grabbed lines on top of the others
			$lines = array_merge($tmp, $lines);

			$maxlines -= $read;

			//next time we want to read another block
			$position -= $bufferlength;

			//don't want to get past the start of file
			$position = max($position, -$filesize);
		}
	}
	fclose($handle);
	$error = URD_NOERROR;
	return $lines;
}


function create_binary_id($subject, $poster)
{
	return md5($subject . ' ' . $poster);
}


function mark_sets(DatabaseConnection $db, $sets, $userID, $type, $status = MARKING_ON, $element='statusint', $manual=FALSE)
{
	global $LN;
	assert (in_array($type, array(USERSETTYPE_GROUP, USERSETTYPE_RSS)));
	assert (is_bool($manual) && is_string($element));
	if ($element != 'statuskill' && $element != 'statusread')
		$element = 'statusint';	
	if ($status != MARKING_NEW_OFF && $status != MARKING_ON && $status != MARKING_OFF)
		throw new exception ($LN['error_invalidstatus'], ERR_INVALID_STATUS);
	if (!is_numeric($userID))
		throw new exception ($LN['error_invaliduserid'], ERR_INVALID_STATUS);
	$ouserID = $userID;
	$db->escape($userID, TRUE);
	$db->escape($type);
	foreach($sets as $set) {
		$setID = $set['ID'];
		$osetID = $setID;
		$db->escape($setID, TRUE);
		$res = $db->select_query("* FROM usersetinfo WHERE \"userID\"= $userID AND \"setID\"= $setID AND \"type\"='$type'", 1);
		if ($res === FALSE)
			$db->insert_query('usersetinfo', array ('setID', 'userID', $element, 'type'), array($osetID, $ouserID, $status, $type));
		else {
			switch ($res[0][$element]) {
				case MARKING_ON:
				case MARKING_NEW_OFF:
					$set_status = $status;
					break;
				case MARKING_OFF:
					if ($manual === TRUE)
						$set_status = $status;
					else 
						$set_status = MARKING_OFF;
					break;
				default:
					throw new exception ($LN['error_invalidstatus'], ERR_INVALID_STATUS);
			}

			$db->escape($set_status, TRUE);
			$qry = "UPDATE usersetinfo SET \"$element\" = $set_status WHERE \"setID\"= $setID AND \"userID\" = $userID";
			$db->execute_query($qry);
		}
	}
}


function unmark_all2(DatabaseConnection $db, $userID, $sets, $element='statusint', $groupID = NULL, $type=USERSETTYPE_GROUP, $marking=MARKING_OFF, $manual = FALSE)
{
	assert (in_array($type, array(USERSETTYPE_GROUP, USERSETTYPE_RSS)) && is_numeric($marking));
	$sets_str = '';
	foreach ($sets as $setid)
		$sets_str .=  ", '$setid'";
	$sets_str = ltrim($sets_str, ', ');
	if ($element != 'statuskill' &&  $element != 'statusread')
		$element = 'statusint';	

	if (!is_numeric($userID))
		throw new exception ($LN['error_invaliduserid'], ERR_INVALID_USERID);
	$orig_userID = $userID;
	$db->escape($userID, TRUE);

	if ($groupID === NULL){
		$qry = "UPDATE usersetinfo SET \"$element\" = " . $marking  . " WHERE \"userID\" = $userID AND \"type\"='$type' AND \"setID\" IN ($sets_str)";
		$db->execute_query($qry);
	} else if (is_numeric($groupID)) {
		$db->escape($groupID, TRUE);
		if ($type == USERSETTYPE_GROUP) {
			$qry = "SELECT setdata.\"ID\" FROM setdata LEFT JOIN usersetinfo AS usi "
			     . " ON usi.\"setID\" = setdata.\"ID\" WHERE usi.\"userID\" = $userID AND setdata.\"groupID\"=$groupID AND \"type\"='$type' AND usi.\"setID\" IN ($sets_str)";
		} else {
			$qry = "SELECT rss_sets.\"setid\" AS \"ID\" FROM rss_sets LEFT JOIN usersetinfo AS usi "
			     . " ON usi.\"setID\" = rss_sets.\"setid\" WHERE usi.\"userID\" = $userID AND rss_sets.\"rss_id\"=$groupID AND \"type\"='$type' AND usi.\"setID\" IN ($sets_str)";
		}
	       	$res = $db->execute_query($qry);
		if ($res !== FALSE)
			mark_sets($db, $res, $orig_userID, $type, $marking, $element, $manual);
	} else 
		throw new exception ($LN['error_invalidgroupid'], ERR_INVALID_USERID);

}


function mark_all2(DatabaseConnection $db, $userID, $sets, $element='statusint', $groupID, $type, $marking=MARKING_ON, $manual = FALSE, $skip_interesting=FALSE)
{ // mark all sets in a given group 
	assert (in_array($type, array(USERSETTYPE_GROUP, USERSETTYPE_RSS)));
	if ($element != 'statuskill' &&  $element != 'statusread')
		$element = 'statusint';	

	if (!is_numeric($userID))
		throw new exception ($LN['error_invaliduserid'], ERR_INVALID_USERID);
	$orig_userID = $userID;
	$db->escape($userID, TRUE);
	$sets_str = '';
	foreach ($sets as $setid)
		$sets_str .=  ", '$setid'";
	$sets_str = ltrim($sets_str, ', ');

	$qint = '';
	if ($skip_interesting)
		$qint = " OR \"statusint\" = '". MARKING_ON ."' ";  
	if ($type == USERSETTYPE_GROUP) {
		if (is_numeric($groupID)) {
			$db->escape($groupID, TRUE);
			$grp = " AND \"groupID\" = $groupID ";
		} else 
			$grp = '';

		$qry = "SELECT * FROM setdata WHERE \"ID\" NOT IN ( SELECT \"setID\" FROM usersetinfo WHERE (\"$element\" = '$marking' $qint) AND \"userID\" = $userID AND \"type\"='" . $type . "' ) AND \"ID\" IN ($sets_str) $grp";
	} else {
		if (is_numeric($groupID)) {
			$db->escape($groupID, TRUE);
			$grp = " AND \"feed_id\" = $groupID ";
		} else 
			$grp = '';

		$qry = "SELECT \"setid\" AS \"ID\" FROM rss_sets WHERE \"setid\" NOT IN (SELECT \"setID\" FROM usersetinfo WHERE (\"$element\" = '$marking' $qint) AND \"userID\" = $userID AND \"type\"='" . $type . "' ) AND \"setid\" IN ($sets_str) $grp";
	}
	$res = $db->execute_query($qry);
	if ($res !== FALSE)
		mark_sets($db, $res, $orig_userID, $type, $marking, $element, $manual);
}


function unmark_all(DatabaseConnection $db, $userID, $element='statusint', $groupID = NULL, $type=USERSETTYPE_GROUP, $marking=MARKING_OFF, $manual = FALSE)
{ // unmark all sets for a user that are already marked in a group
	global $LN;
	assert (in_array($type, array(USERSETTYPE_GROUP, USERSETTYPE_RSS)) && is_numeric($marking));
	if ($element != 'statuskill' &&  $element != 'statusread')
		$element = 'statusint';	

	if (!is_numeric($userID))
		throw new exception ($LN['error_invaliduserid'], ERR_INVALID_USERID);
	$orig_userID = $userID;
	$db->escape($userID, TRUE);

	if ($groupID === NULL){
		$qry = "UPDATE usersetinfo SET \"$element\" = " . $marking  . " WHERE \"userID\" = $userID AND \"type\"='" . $type . "'";
		$db->execute_query($qry);
	} else if (is_numeric($groupID)) {
		$db->escape($groupID, TRUE);
		if ($type == USERSETTYPE_GROUP) {
			$qry = "SELECT setdata.\"ID\" FROM setdata LEFT JOIN usersetinfo AS usi "
			     . " ON usi.\"setID\" = setdata.\"ID\" WHERE usi.\"userID\" = $userID AND setdata.\"groupID\"=$groupID AND \"type\"='" . $type . "'";
		} else {
			$qry = "SELECT rss_sets.\"setid\" AS \"ID\" FROM rss_sets LEFT JOIN usersetinfo AS usi "
			     . " ON usi.\"setID\" = rss_sets.\"setid\" WHERE usi.\"userID\" = $userID AND rss_sets.\"rss_id\"=$groupID AND \"type\"='" . $type . "'";
		}
	       	$res = $db->execute_query($qry);
		if ($res !== FALSE)
			mark_sets($db, $res, $orig_userID, $type, $marking, $element, $manual);
	} else 
		throw new exception ($LN['error_invalidgroupid'], ERR_INVALID_USERID);
}


function mark_all(DatabaseConnection $db, $userID, $element='statusint', $groupID, $type, $marking=MARKING_ON, $manual = FALSE)
{ // mark all sets in a given group 
	assert (in_array($type, array(USERSETTYPE_GROUP, USERSETTYPE_RSS)));
	if ($element != 'statuskill' &&  $element != 'statusread')
		$element = 'statusint';	

	if (!is_numeric($userID))
		throw new exception ($LN['error_invaliduserid'], ERR_INVALID_USERID);
	$orig_userID = $userID;
	$db->escape($userID, TRUE);
	if ($type == USERSETTYPE_GROUP) {
		if (is_numeric($groupID)) {
			$db->escape($groupID, TRUE);
			$grp = " AND \"groupID\" = $groupID ";
		} else 
			$grp = '';

		$qry = "SELECT * FROM setdata WHERE \"ID\" NOT IN ( SELECT \"setID\" FROM usersetinfo WHERE \"$element\" = '$marking' AND \"userID\" = $userID AND \"type\"='" . $type . "') $grp";
	} else {
		if (is_numeric($groupID)) {
			$db->escape($groupID, TRUE);
			$grp = " AND \"feed_id\" = $groupID ";
		} else 
			$grp = '';

		$qry = "SELECT \"setid\" AS \"ID\" FROM rss_sets WHERE \"setid\" NOT IN (SELECT \"setID\" FROM usersetinfo WHERE \"$element\" = '$marking' AND \"userID\" = $userID AND \"type\"='" . $type . "') $grp";
	}
	$res = $db->execute_query($qry);
	if ($res !== FALSE)
		mark_sets($db, $res, $orig_userID, $type, $marking, $element, $manual);
}


function get_match_terms(DatabaseConnection $db, $terms, $userID)
{
	$search_terms = trim(get_pref($db, $terms, $userID, ''));
	if ($search_terms == '')
		return FALSE;
	$search_terms = clean_textarea_data($search_terms);
	$terms = explode("\n", $search_terms);
	return $terms;
}


function expand_search_terms_as_query(DatabaseConnection $db, $terms, $search_type, array $fields)
{
	assert(count($fields) >= 2);

	$like_a = $like_b = $like1 = $like2 = '';
	reset($fields);
	$table1 = key($fields);
	$column1 = current($fields);
	next($fields);
	$table2 = key($fields);
	$column2 = current($fields);
    foreach ($terms as $term) {
    $term = trim($term);
		if ($term == '') 
			continue;
		$words = explode(' ', $term);
		$like1 = '1=1';
		$like2 = '1=1';
        foreach ($words as $word) {
            $word = trim($word);
			if ($word == '') continue;
            if ($word[0] == '-') {
                $not = 'NOT';
                $word = ltrim($word, '-');
            } else
                $not = '';
			if ($search_type == 'REGEXP')
				$word = ".*$word.*";
			else 
				$word = "%$word%";
			$db->escape($word, TRUE);
			$like1 .= " AND ($not $table1.\"$column1\" $search_type $word)";
			$like2 .= " AND ($not $table2.\"$column2\" $search_type $word)";
		}
		$like_a .= " OR ($like1)";
		$like_b .= " OR ($like2)" ;
	}
	return array($like_a, $like_b);
}

function mark_search_rss(DatabaseConnection $db, $userID, $element = 'statusint', $feed_id=NULL)
	// used to automatically mark sets interesting or kill from the search terms / blocked terms
{
	global $LN;
	assert(is_numeric($userID));
	if ($element == 'statuskill')
		$terms = 'blocked_terms';
	/*	else if $element == 'statusread')
	$terms = '' */ // todo
	else {
		$element = 'statusint';	
		$terms = 'search_terms';
	}
	$search_type = strtoupper(get_pref($db, 'search_type', $userID, 'LIKE'));
	if ($search_type != 'REGEXP' && $search_type != 'LIKE')
		$search_type = 'LIKE';
	$search_type = $db->get_pattern_search_command($search_type); // postgres doesn't like regexp, but uses similar .... I just love standards
	$terms = get_match_terms($db, $terms, $userID);
	if ($terms === FALSE)
		return;
	list ($like_setdata, $like_extsetdata) = expand_search_terms_as_query($db, $terms, $search_type, array('rss_sets'=> 'setname', 'extsetdata' => 'value'));
	if ($feed_id === NULL)
		$feed = '';
	else if (is_numeric($feed_id)) {
		$db->escape($feed_id, TRUE);
		$res = $db->select_query("\"id\" FROM rss_urls WHERE \"subscribed\" = '" . RSS_SUBSCRIBED . "' AND \"id\"=$feed_id", 1);
		if ($res === FALSE) 
			throw new exception($LN['error_feednotfound'], ERR_RSS_NOT_FOUND);
		$feed = " \"rss_id\"=$feed_id AND";	
	} else
		throw new exception ($LN['error_invalidfeedid'], ERR_RSS_NOT_FOUND);
	if ($like_setdata == '') 
		$like_setdata = " AND 1=0 ";
	if ($like_extsetdata == '')
		$like_extsetdata = " AND 1=0 ";

	$type = USERSETTYPE_RSS;

	$sql = "(SELECT rss_sets.\"setid\" AS \"ID\" FROM rss_sets WHERE $feed  (1=0 $like_setdata)) "; 
	$sql .= "UNION (SELECT rss_sets.\"setid\" AS \"ID\" FROM rss_sets, extsetdata WHERE $feed \"type\"='$type' AND rss_sets.\"setid\" = extsetdata.\"setID\" AND extsetdata.\"name\" = 'setname' AND (1=0 $like_extsetdata))";
	$res = $db->execute_query($sql); // query is still incorrect??
	if ($res !== FALSE)
		mark_sets($db, $res, $userID, USERSETTYPE_RSS, MARKING_ON, $element, FALSE);
}


function mark_search(DatabaseConnection $db, $userID, $element = 'statusint', $groupID=NULL)
	// used to automatically mark sets interesting or kill from the search terms / blocked terms
{
	global $LN;
	assert(is_numeric($userID));
	if ($element == 'statuskill')
		$terms = 'blocked_terms';
	/*	else if $element == 'statusread')
	$terms = '' */ // todo
	else {
		$element = 'statusint';	
		$terms = 'search_terms';
	}
	$search_type = strtoupper(get_pref($db, 'search_type', $userID, 'LIKE'));
	if ($search_type != 'REGEXP' && $search_type != 'LIKE')
		$search_type = 'LIKE';
	$search_type = $db->get_pattern_search_command($search_type); // postgres doesn't like regexp, but uses similar .... I just love standards
	$terms = get_match_terms($db, $terms, $userID);
	if ($terms === FALSE)
		return;
    list ($like_setdata, $like_extsetdata) = expand_search_terms_as_query($db, $terms, $search_type, array('setdata'=> 'subject', 'extsetdata' => 'value'));
	if ($groupID === NULL)
		$group = '';
	else if (is_numeric($groupID)) {
		$db->escape($groupID, TRUE);
		$res = $db->select_query("\"ID\" FROM groups WHERE active = '" . NG_SUBSCRIBED . "' AND \"ID\"=$groupID", 1);
		if ($res === FALSE) 
			throw new exception($LN['error_groupnotfound'], ERR_GROUP_NOT_FOUND);
		$group = " \"groupID\"=$groupID AND";	
	} else
		throw new exception ($LN['error_invalidgroupid'], ERR_GROUP_NOT_FOUND);
	if ($like_setdata == '') 
		$like_setdata = ' AND 1=0 ';
	if ($like_extsetdata == '')
		$like_extsetdata = ' AND 1=0 ';

	$type = USERSETTYPE_GROUP;
	$sql = "(SELECT setdata.\"ID\" FROM setdata WHERE $group (1=0 $like_setdata)) "; 
    $sql .= "UNION (SELECT setdata.\"ID\" FROM setdata, extsetdata WHERE $group \"type\"='$type' AND setdata.\"ID\" = extsetdata.\"setID\" AND extsetdata.\"name\" = 'setname' AND (1=0 $like_extsetdata))";
	$res = $db->execute_query($sql);
	if ($res !== FALSE)
		mark_sets($db, $res, $userID, USERSETTYPE_GROUP, MARKING_ON, $element, FALSE);
}


function download_sets(DatabaseConnection $db, array $sets, $userID, $type)
{
	echo_debug_function(DEBUG_WORKER, __FUNCTION__);
	assert (in_array($type, array(USERSETTYPE_GROUP, USERSETTYPE_RSS)) && is_numeric($userID));
	$rprefs = load_config($db);
	$uc = new urdd_client($rprefs['urdd_host'], $rprefs['urdd_port'], $userID);
	if (!$uc->is_connected()) 
		throw new exception($LN['error_urddconnect']);
	$marking = MARKING_ON;
	$db->escape($marking);
	$db->escape($userID);
	$db->escape($type);
	$max_dl_name = get_config($db, 'max_dl_name');
	foreach($sets as $set) {
		$size = $set['size'];
		$setid = $set['ID'];
		if (isset($set['value']))
			$dlname = find_name($db, $set['value']); 
		else 
			$dlname = find_name($db, $set['subject']); 
		$result = $uc->create_download();
		if ($result === FALSE) 
			throw new exception($LN['error_createdlfailed']);
		$result = $uc->decode($result);

		// Download ID:
		$dlid = $result[0];
		$dlthreads = $result[1];
		$db->escape($setid);

		set_download_name($db, $dlid, $dlname);
		if ($type == USERSETTYPE_GROUP) {
			$uc->add_set_data($dlid, $setid);
		} else {
			$sql = "SELECT nzb_link FROM rss_sets WHERE \"setid\" = '$setid'";
			$res = $db->execute_query($sql);
			if ($res !== FALSE) {
				$url = $res[0]['nzb_link'];
				$uc->parse_nzb($url, $dlid); 
				// mark user info
			} else {
				throw new exception('Link not found');
			}
		}

		$res = $db->execute_query("SELECT * FROM usersetinfo WHERE \"userID\" = '$userID' AND \"setID\" = '$setid' AND \"type\"='$type'");
		if ($res === FALSE)
			$qry = "INSERT INTO usersetinfo (\"setID\", \"userID\", \"statusread\", \"type\") VALUES ('$setid', '$userID', '$marking', '$type')";
		else
			$qry = "UPDATE usersetinfo SET \"statusread\" = '$marking' WHERE \"setID\" = '$setid' AND \"userID\" = '$userID' AND \"type\"='$type'";
		$db->execute_query($qry);
		set_download_size($db, $dlid, $size);
		$stat_id = add_stat_data($db, STAT_ACTION_DOWNLOAD, 0, $userID);
		set_stat_id($db, $dlid, $stat_id);
		foreach ($dlthreads as $id) {
			set_start_time($db, $dlid, time());
			usleep(500000);
			$uc->unpause($id);
		}
	}
	$uc->disconnect();
}


function auto_download_rss(DatabaseConnection $db, $userID, $feed_id=NULL)
{ // works only for index sets yet
	echo_debug_function(DEBUG_WORKER, __FUNCTION__);
	assert(is_numeric($userID));
    if (!is_autodownloader($db, $userID)) return;
	$type = USERSETTYPE_RSS;
	$search_type = strtoupper(get_pref($db, 'search_type', $userID, 'LIKE'));
	if ($search_type != 'REGEXP' && $search_type != 'LIKE')
		$search_type = 'LIKE';

	$search_type = $db->get_pattern_search_command($search_type); // postgres doesn't like regexp, but uses similar .... I just love standards
	$blocked_terms = 'blocked_terms';
	$search_terms = 'search_terms';
	$search_terms = get_match_terms($db, $search_terms , $userID, '');
	$like_setdata_true = $like_extsetdata_true = $like_setdata_false = $like_extsetdata_false = '';
	if ($search_terms === FALSE)
		return;
	list ($like_setdata_true, $like_extsetdata_true) = expand_search_terms_as_query($db, $search_terms, $search_type, array('rss_sets'=> 'setname', 'extsetdata' => 'value'));
	$blocked_terms = get_match_terms($db, $blocked_terms , $userID, '');
	if ($blocked_terms !== FALSE)
		list ($like_setdata_false, $like_extsetdata_false) = expand_search_terms_as_query($db, $blocked_terms, $search_type,  array('rss_sets'=> 'setname', 'extsetdata' => 'value'));
	else 
		$like_false = '';

	if ($feed_id === NULL)
		$feed = '';
	else if (is_numeric($feed_id)) {
		$db->escape($feed_id, TRUE);
		$res = $db->select_query("\"id\" FROM rss_urls WHERE subscribed = '" . RSS_SUBSCRIBED . "' AND \"id\"=$feed_id", 1);
		if ($res === FALSE) 
			throw new exception($LN['error_feednotfound'], ERR_RSS_NOT_FOUND);
		$feed = " AND rss_sets.\"rss_id\"=$feed_id ";	
	} else
		throw new exception ($LN['error_invalidfeedid'], ERR_RSS_NOT_FOUND);

	if ($like_setdata_true != '' && $like_setdata_false != '') 
		$like_setdata = " (1=0 $like_setdata_true) AND NOT (1=0 $like_setdata_false)";
	else if ($like_setdata_true != '') 
		$like_setdata = " (1=0 $like_setdata_true)";
	else  // we don't do anything
		$like_setdata = ' 1=0';
	if ($like_extsetdata_true != '' && $like_extsetdata_false != '') 
		$like_extsetdata = " (1=0 $like_extsetdata_true) AND NOT (1=0 $like_extsetdata_false)";
	else if ($like_extsetdata_true != '') 
		$like_extsetdata = " (1=0 $like_extsetdata_true)";
	else  // we don't do anything
		$like_extsetdata = ' 1=0';
	$prefs = load_prefs_clean($db, $userID); // load the prefs

	if (isset($prefs['maxsetsize'])) 
		$maxsetsize = $prefs['maxsetsize'];
	else 
		$maxsetsize = 0;

	if (isset($prefs['minsetsize'])) 
		$minsetsize = $prefs['minsetsize'];
	else 
		$minsetsize = 0;

	$Qsize = "( rss_sets.\"size\" >= ((SELECT CASE WHEN \"minsetsize\" IS NULL THEN $minsetsize ELSE minsetsize END) * 1024 * 1024))";

	if ($maxsetsize == 0)
		$Qsize .= " AND (userfeedinfo.\"maxsetsize\" = 0 OR \"maxsetsize\" IS NULL OR rss_sets.\"size\" <= ( \"maxsetsize\" * 1024 * 1024)) ";
	else 
		$Qsize .= " AND (userfeedinfo.\"maxsetsize\" = 0 OR (rss_sets.\"size\" <= ((SELECT CASE WHEN \"maxsetsize\" IS NULL THEN $maxsetsize ELSE \"maxsetsize\" END) * 1024 * 1024 ))) ";

	$Qsize = " AND (rss_sets.\"size\" = 0 OR ($Qsize) )"; // size == 0 is special in rss feeds --> size unknown

    $Qvisible = " AND (userfeedinfo.visible IS NULL OR userfeedinfo.visible = 1)";

	$sql  = "SELECT rss_sets.\"setid\" AS \"ID\", rss_sets.\"size\", extsetdata.\"value\", rss_sets.\"setname\" AS \"subject\" FROM rss_sets ";
	$sql .= "LEFT JOIN usersetinfo ON rss_sets.\"setid\" = usersetinfo.\"setID\" AND usersetinfo.\"type\"='$type' and usersetinfo.\"userID\" = '$userID'";
	$sql .= "LEFT JOIN userfeedinfo ON rss_sets.\"rss_id\" = userfeedinfo.\"feedid\" AND userfeedinfo.\"userid\" = '$userID' ";
	$sql .= "LEFT JOIN extsetdata ON rss_sets.\"setid\" = extsetdata.\"setID\" AND extsetdata.\"name\" = 'setname' AND  extsetdata.\"type\"='$type' ";
	$sql .= "WHERE (usersetinfo.\"statusread\" != 1 OR usersetinfo.\"statusread\" IS NULL) AND ";
	$sql .= "(usersetinfo.\"statuskill\" != 1 OR usersetinfo.\"statuskill\" IS NULL) $Qvisible AND (($like_extsetdata)  OR ($like_setdata)) $feed $Qsize";

	$res = $db->execute_query($sql);
	try {
		if ($res !== FALSE)
			download_sets($db, $res, $userID, USERSETTYPE_RSS);
	} catch (exception $e) {
		write_log('Something went wrong while auto-downloading', LOG_NOTICE);
	}
}


function auto_download(DatabaseConnection $db, $userID, $groupID=NULL)
{ // works only for index sets yet
	echo_debug_function(DEBUG_WORKER, __FUNCTION__);
    assert(is_numeric($userID));
    if (!is_autodownloader($db, $userID)) return;
	$like_setdata_true = $like_extsetdata_true = $like_setdata_false = $like_extsetdata_false = '';
	$search_type = strtoupper(get_pref($db, 'search_type', $userID, 'LIKE'));
	if ($search_type != 'REGEXP' && $search_type != 'LIKE')
		$search_type = 'LIKE';

	$search_type = $db->get_pattern_search_command($search_type); // postgres doesn't like regexp, but uses similar .... I just love standards
	$blocked_terms = 'blocked_terms';
	$search_terms = 'search_terms';
	$search_terms = get_match_terms($db, $search_terms , $userID, '');
	if ($search_terms === FALSE)
		return;
	list ($like_setdata_true, $like_extsetdata_true) = expand_search_terms_as_query($db, $search_terms, $search_type, array('setdata'=> 'subject', 'extsetdata' => 'value'));
	$blocked_terms = get_match_terms($db, $blocked_terms , $userID, '');
	if ($blocked_terms !== FALSE)
		list ($like_setdata_false, $like_extsetdata_false) = expand_search_terms_as_query($db, $blocked_terms, $search_type,  array('setdata'=> 'subject', 'extsetdata' => 'value'));
	else 
		$like_false = '';

	if ($groupID === NULL)
		$group = '';
	else if (is_numeric($groupID)) {
		$db->escape($groupID, TRUE);
		$res = $db->select_query("\"ID\" FROM groups WHERE active = '" . NG_SUBSCRIBED . "' AND \"ID\"=$groupID", 1);
		if ($res === FALSE) 
			throw new exception($LN['error_groupnotfound'], ERR_GROUP_NOT_FOUND);
		$group = " AND setdata.\"groupID\"=$groupID ";	
	} else
		throw new exception ($LN['error_invalidgroupid'], ERR_GROUP_NOT_FOUND);

	if ($like_setdata_true != '' && $like_setdata_false != '') 
		$like_setdata = "  (1=0 $like_setdata_true) AND NOT (1=0 $like_setdata_false)";
	else if ($like_setdata_true != '') 
		$like_setdata = "  (1=0 $like_setdata_true)";
	else  // we don't do anything
		$like_setdata = '  1=0';
	if ($like_extsetdata_true != '' && $like_extsetdata_false != '') 
		$like_extsetdata = "  (1=0 $like_extsetdata_true) AND NOT (1=0 $like_extsetdata_false)";
	else if ($like_extsetdata_true != '') 
		$like_extsetdata = "  (1=0 $like_extsetdata_true)";
	else  // we don't do anything
		$like_extsetdata = ' 1=0';
	$type = USERSETTYPE_GROUP;
	
	$prefs = load_prefs_clean($db, $userID); // load the prefs

	if (isset($prefs['maxsetsize'])) 
		$maxsetsize = $prefs['maxsetsize'];
	else 
		$maxsetsize = 0;

	if (isset($prefs['minsetsize']))
		$minsetsize = $prefs['minsetsize'];
	else 
		$minsetsize = 0;

	$Qcomplete = " AND (\"articlesmax\"=0 OR floor(\"binaries\" * 100 / GREATEST(1, \"articlesmax\")) >= {$prefs['setcompleteness']})";/// euah ... the horror... but it is ansi sql compliant... no refers to as fields in where clauses ...
	$Qsize = " AND ( setdata.\"size\" >= ((SELECT CASE WHEN \"minsetsize\" IS NULL THEN $minsetsize ELSE minsetsize END) * 1024 * 1024))";

	if  ($maxsetsize == 0)
		$Qsize .= " AND (usergroupinfo.\"maxsetsize\" = 0 OR maxsetsize IS NULL OR setdata.size <= ( maxsetsize * 1024 * 1024)) ";
	else 
		$Qsize .= " AND (usergroupinfo.\"maxsetsize\" = 0 OR (setdata.size <= ((SELECT CASE WHEN maxsetsize IS NULL THEN $maxsetsize ELSE maxsetsize END) * 1024 * 1024 ))) ";
    
    $Qvisible = " AND (usergroupinfo.visible IS NULL OR usergroupinfo.visible = 1)";

	$sql  = "SELECT setdata.\"ID\", setdata.\"size\", extsetdata.\"value\", setdata.\"subject\" FROM setdata ";
	$sql .= "LEFT JOIN usersetinfo ON setdata.\"ID\" = usersetinfo.\"setID\" AND usersetinfo.\"type\"='$type' and usersetinfo.\"userID\" = '$userID'";
	$sql .= "LEFT JOIN usergroupinfo ON setdata.\"groupID\" = usergroupinfo.\"groupid\" AND usergroupinfo.\"userid\" = '$userID' ";
	$sql .= "LEFT JOIN extsetdata ON setdata.\"ID\" = extsetdata.\"setID\" AND extsetdata.\"name\" = 'setname' AND extsetdata.\"type\"='$type' ";
	$sql .= "WHERE (usersetinfo.\"statusread\" != 1 OR usersetinfo.\"statusread\" IS NULL) AND ";
	$sql .= "(usersetinfo.\"statuskill\" != 1 OR usersetinfo.\"statuskill\" IS NULL) $Qvisible AND (($like_extsetdata) OR ($like_setdata)) $group $Qsize $Qcomplete";
	$res = $db->execute_query($sql);
	try {
		if ($res !== FALSE)
			download_sets($db, $res, $userID, USERSETTYPE_GROUP);
	} catch (exception $e) {
		write_log('Something went wrong while auto-downloading', LOG_NOTICE);
	}
}


function read_argv()
{
	global $argv;
	if (!isset ($arvg) || !is_array($argv)) {
		if (!isset($_SERVER['argv']) || !is_array($_SERVER['argv'])) {
			if (!isset ($GLOBALS['HTTP_SERVER_VARS']['argv']) || !is_array($GLOBALS['HTTP_SERVER_VARS']['argv'])) 
				throw new exception($LN['error_couldnotreadargs'], ERR_INVALID_ARGUMENT);
			return $GLOBALS['HTTP_SERVER_VARS']['argv'];
		}
		return $_SERVER['argv'];
	}
	return $argv;
}


function clean_config(DatabaseConnection $db)
{
	$sql = "DELETE FROM preferences WHERE \"userID\" = 0";
	$db->execute_query($sql);
}


function clean_pref(DatabaseConnection $db, $userID)
{
	assert(is_numeric($userID));
	if ($userID == 0)
		throw new exception ($LN['error_resetnotallowed'], ERR_ACCESS_DENIED);
	$db->escape($userID, TRUE);
	$sql = "DELETE FROM preferences WHERE \"userID\" = $userID";
	$db->execute_query($sql);
}


function reset_pref(DatabaseConnection $db, $userID)
{
	assert(is_numeric($userID));
	if ($userID == 0)
		throw new exception ($LN['error_resetnotallowed'], ERR_ACCESS_DENIED);
	$prefArray = get_default_prefs();

	foreach ($prefArray as $var => $val)
		set_pref($db, $var, $val, $userID);
}


function reset_config(DatabaseConnection $db)
{
	$prefArray = get_default_config();
	foreach ($prefArray as $var => $val) 
		set_pref($db, $var, $val, 0);
}


function clean_textarea_data($text)
{
	$text = str_replace('\n', "\n", $text);
	$text = str_replace('\r', "\r", $text);
	$text = str_replace("\r", '', $text);
	$text = stripslashes($text);
	return $text;
}


function get_hiddenfiles($text)
{
	$text = clean_textarea_data($text);
	$list = explode ("\n", $text);
	$ll = array();
	foreach ($list as $l) {
		$l = trim($l);
		if ($l != '')
			$ll[] = $l;
	}
	return  $ll;
}


function clean_list($list) // removes empty lines from text_areas
{
	$list = explode("\n", $list);
	$ll = array();
	foreach ($list as $l) {
		$l = trim($l);
		if ($l != '')
			$ll[] = $l;
	}
	return implode ("\n", $ll);
}


function read_system_groups()
{
	global $LN;
	$g = @file (GROUPS_FILE, FILE_IGNORE_NEW_LINES);
	if ($g === FALSE)
		throw new exception ($LN['error_filenotfound'] . ' ' . GROUPS_FILE, ERR_FILE_NOT_FOUND);

	foreach ($g as $grp) {
		$r = explode (':', $grp);
		if ($r === FALSE) continue;
		$grps[] = $r[0];
	}
	return $grps;
}


function find_unique_name($base, $part, $dlname, $extension='', $is_file=FALSE)
{
	$appendix = '';
	$cntr = 1;
	do {
		$to = $base . $part . $dlname. $appendix . (($is_file !== TRUE) ? '/' : '') . $extension;
		$appendix = "_$cntr";
		$cntr++;
	} while (file_exists($to));
	return $to;
}


function XMLRPC_to_URD_central_server($function, array $params)
{
	assert(is_string($function));
	global $config;
	if (isset($config['connect_urdland']) && $config['connect_urdland'] !== TRUE)
		return FALSE;

	if (!extension_loaded ('xmlrpc')) {
		write_log('XMLRPC module not loaded', LOG_ERR);
		return FALSE;
	}

	// Function = 'GetSetInfo' or 'SendSetInfo'
	// Params = array of the parameters
	$request = xmlrpc_encode_request($function, $params, array('verbosity'=>'newlines_only'));

	// Send data as POST:
	$request = urlencode($request);
	$request = 'message=' . $request;
	static $url =  array (URDLAND_PATH_S, // try https first, but currently we don't have that @ urdland
		URDLAND_PATH);
	static $cached_url = NULL; // we store the URL as it probably remains valid for a while... an error may just be temporary
	static $cached_time = 0;
	try {
		if ($cached_url == NULL || ($cached_time + 3600) < time()) {
			// Check if URL is valid:
			$cached_url = NULL;
			foreach ($url as $oneurl) {
				$result = test_url($oneurl);
				echo_debug($oneurl . ' : ' . $result, DEBUG_HTTP);

				if ($result  == '200') { // Everything OK
					$cached_url = $oneurl;
					$cached_time = time();
					echo_debug("Found URDLand server $cached_url", DEBUG_HTTP);
					break;
				}
			}
		} else
			echo_debug("Using cached server $cached_url", DEBUG_HTTP);

		if ($cached_url == NULL) {
			write_log('No URDLand server available', LOG_ERR);
			return FALSE;
		}
		$header[] = 'Content-type: application/x-www-form-urlencoded';
		$header[] = 'Content-length: '.strlen($request);
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_URL, $cached_url);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($ch, CURLOPT_TIMEOUT, 300);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
		curl_setopt($ch, CURLOPT_ENCODING, 'gzip, deflate');
		curl_setopt($ch, CURLOPT_POSTFIELDS, $request);

		$data = curl_exec($ch);
		if (curl_errno($ch)) {
			write_log(curl_error($ch), LOG_ERR);
			$cached_url = NULL;
			return FALSE;
		} else 
			curl_close($ch);

		$response = xmlrpc_decode($data);

		// Check for error:
		if (is_array($response) && isset($response[0]) && $response[0] == 'Error') {
			write_log('Error while communicating with master server: ' . $response[1], LOG_ERR);
			$cached_url = NULL;
			return FALSE;
		}
		return $response;
	} catch (exception $e) {
		write_log('Exception while talking to the URD central server!', LOG_ERR);
		$cached_url = NULL;
		return FALSE;
	}

	// None of the URLs returned code 200 (the normal 'everything is ok' code).
	// This is bad.
	write_log('None of the central server URLs worked!', LOG_ERR);
	return FALSE;
}


function test_url($url)
{
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL, $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
	$data = @curl_exec($ch);
	return curl_getinfo($ch, CURLINFO_HTTP_CODE);
}


function feed_name(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$res = $db->select_query("\"name\" FROM rss_urls WHERE \"id\"=$id", 1);
	if ($res !== FALSE)
		return $res[0]['name'];
	else 
		throw new exception('Feed not found', ERR_RSS_NOT_FOUND);
}


function group_expire(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$res = $db->select_query("\"expire\" FROM groups WHERE \"ID\"=$id", 1);
	if ($res !== FALSE)
		return $res[0]['expire'];
	else 
		throw new exception('Group not found', ERR_GROUP_NOT_FOUND);
}

function group_by_name(DatabaseConnection $db, $name)
{
	$db->escape($name, TRUE);
	$res = $db->select_query("\"ID\" FROM groups WHERE \"name\"=$name", 1);
	if ($res !== FALSE)
		return $res[0]['ID'];
	else 
		throw new exception('Group not found', ERR_GROUP_NOT_FOUND);
}


function feed_by_name(DatabaseConnection $db, $name)
{
	$db->escape($name, TRUE);
	$res = $db->select_query("\"id\" FROM rss_urls WHERE \"name\"=$name", 1);
	if ($res !== FALSE)
		return $res[0]['id'];
	else 
		throw new exception('Feed not found', ERR_RSS_NOT_FOUND);
}


function group_name(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$res = $db->select_query("\"name\" FROM groups WHERE \"ID\"=$id", 1);
	if ( $res !== FALSE)
		return $res[0]['name'];
	else 
		throw new exception('Group not found', ERR_GROUP_NOT_FOUND);
}


function group_exists(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$res = $db->select_query("\"ID\" FROM groups WHERE \"ID\"=$id", 1);
	return $res !== FALSE;
}


function feed_subscribed(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$res = $db->select_query("\"subscribed\" FROM rss_urls WHERE \"id\"=$id", 1);
	if ($res === FALSE) 
		throw new exception('Feed not found', ERR_RSS_NOT_FOUND);
	else 
		return $res[0]['subscribed'] == RSS_SUBSCRIBED;
}


function group_subscribed(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$res = $db->select_query("\"active\" FROM groups WHERE \"ID\"=$id", 1);
	if ($res === FALSE) 
		throw new exception('Group not found', ERR_GROUP_NOT_FOUND);
	else 
		return $res[0]['active'] == NG_SUBSCRIBED;
}


function update_feed_state(DatabaseConnection $db, $id, $state, $exp)
{
	assert(is_numeric($id) && is_numeric($exp));
	$db->escape($id, TRUE);
	$db->escape($exp, TRUE);
	$db->escape($state, TRUE);
	$query = "UPDATE rss_urls SET \"subscribed\"=$state, \"expire\"=$exp WHERE \"id\"=$id";
	$db->execute_query($query);
}


function update_group_state(DatabaseConnection $db, $id, $state, $exp)
{
	assert(is_numeric($id) && is_numeric($exp));
	$db->escape($id, TRUE);
	$db->escape($exp, TRUE);
	$db->escape($state, TRUE);
	$query = "UPDATE groups SET \"active\"=$state, \"expire\"=$exp WHERE \"ID\"=$id";
	$db->execute_query($query);
}


function get_active_users(DatabaseConnection $db)
{
	$sql = "SELECT \"ID\" FROM users WHERE \"active\" = '" . USER_ACTIVE . "'";
	return $db->execute_query($sql);
}


function mark_interesting_rss(DatabaseConnection $db, $feed_id)
{
	assert(is_numeric($feed_id));
	echo_debug_function(DEBUG_WORKER, __FUNCTION__);
	$auto_download = get_config_default($db, 'auto_download', 0);

	$res = get_active_users($db);
	if ($res === FALSE)
		write_log('No active users found', LOG_NOTICE);

	foreach ($res as $row) {
		$user_id = $row['ID'];
		mark_search_rss($db, $user_id, 'statuskill', $feed_id);
		if ($auto_download > 0  && get_pref($db, 'use_auto_download', $user_id, 0) > 0)
			auto_download_rss($db, $user_id, $feed_id);
		else 
			mark_search_rss($db, $user_id, 'statusint', $feed_id);
	}
}


function get_rar_files(DatabaseConnection $db, $postid)
{
    $sql = "SELECT \"rarfile\", count(\"id\") AS \"rar_count\" FROM post_files WHERE \"postid\" = $postid GROUP BY \"rarfile\"";
    $res = $db->execute_query($sql);
    if ($res === FALSE) 
        throw new exception('No files to post');

    $rarfile_count = array();
    foreach ($res as $row) {
        $rarfile_count[$row['rarfile']] = $row['rar_count'];
    }
    return $rarfile_count;
}


function mark_interesting(DatabaseConnection $db, $groupID)
{
	assert(is_numeric($groupID));
	echo_debug_function(DEBUG_WORKER, __FUNCTION__);
	$auto_download = get_config_default($db, 'auto_download', 0);

	$res = get_active_users($db);
	if ($res === FALSE)
		write_log('No active users found', LOG_NOTICE);

	foreach ($res as $row) {
		$user_id = $row['ID'];
		mark_search($db, $user_id, 'statuskill', $groupID);
		if ($auto_download > 0  && get_pref($db, 'use_auto_download', $user_id, 0) > 0)
			auto_download($db, $user_id, $groupID);
		else 
			mark_search($db, $user_id, 'statusint', $groupID);
	}
}


function get_post_articles_count(DatabaseConnection $db, $postid)
{
     $sql = "SELECT count(\"id\") AS \"total_count\" FROM post_files WHERE \"postid\" = '$postid'";
     $res = $db->execute_query($sql);
     if ($res === FALSE || !isset($res[0]['total_count']))
         throw new exception('No files to post');

     return $res[0]['total_count'];
}


function get_post_articles_count_status(DatabaseConnection $db, $id, $status)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$db->escape($status, TRUE);
	$query = "SELECT count(\"id\") AS cnt FROM post_files WHERE \"postid\"=$id AND \"status\"=$status";
	$res = $db->execute_query($query);
	return $res[0]['cnt'];
}


function get_download_articles_count_status(DatabaseConnection $db, $id, $status)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$db->escape($status, TRUE);
	$query = "SELECT count(\"ID\") AS cnt FROM downloadarticles WHERE \"downloadID\"=$id AND \"status\"=$status";
	$res = $db->execute_query($query);
	return $res[0]['cnt'];
}


function get_download_articles_count(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$query = "SELECT count(\"ID\") AS cnt FROM downloadarticles WHERE \"downloadID\"=$id";
	$res = $db->execute_query($query);
	return $res[0]['cnt'];
}


function update_post_articles(DatabaseConnection $db, $ready_status, $id, $dl_status)
{
    assert(is_numeric($id));
    $db->escape($id, TRUE);
	$db->escape($status, TRUE);
    $db->escape($oldstatus, TRUE);
	$query = "UPDATE post_files SET \"status\"=$ready_status WHERE \"postid\"=$id AND \"status\"=$dl_status";
	$db->execute_query($query);
}


function update_dlinfo_firstrun(DatabaseConnection $db, $id, $first_run)
{
    assert(is_numeric($id));
    $first_run = (int)$first_run;
	$db->escape($id, TRUE);
	$db->escape($first_run, TRUE);
	$query = "UPDATE downloadinfo SET \"first_run\"=$first_run WHERE \"ID\"=$id";
	$db->execute_query($query);
}


function update_postinfo_status(DatabaseConnection $db, $status, $id, $oldstatus=NULL)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$db->escape($status, TRUE);
	$query = "UPDATE postinfo SET \"status\"=$status WHERE \"id\"=$id";
	if ($oldstatus !== NULL) {
		$db->escape($oldstatus, TRUE);
		$query .= " AND status=$oldstatus";
	}
	$db->execute_query($query);
}


function update_postinfo_size(DatabaseConnection $db,$id, $size)
{
	assert(is_numeric($id) && is_numeric($size));
	$db->escape($id, TRUE);
	$db->escape($size, TRUE);
	$query = "UPDATE postinfo SET \"size\"=$size WHERE \"id\"=$id";
	$db->execute_query($query);
}


function update_dlinfo_status(DatabaseConnection $db, $status, $id, $oldstatus=NULL)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$db->escape($status, TRUE);
	$query = "UPDATE downloadinfo SET \"status\"=$status WHERE \"ID\"=$id";
	if ($oldstatus !== NULL) {
		$db->escape($oldstatus, TRUE);
		$query .= " AND status=$oldstatus";
	}
	$db->execute_query($query);
}


function get_download_password(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$query = "\"password\" FROM downloadinfo WHERE \"ID\"=$id";
	$res = $db->select_query($query, 1);
	if ($res === FALSE)
		throw new exception ('Download not found', ERR_DOWNLOAD_NOT_FOUND);
	return $res[0]['password'];
}


function get_download_destination(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$query = "\"destination\" FROM downloadinfo WHERE \"ID\"=$id";
	$res = $db->select_query($query, 1);
	if ($res === FALSE)
		throw new exception ('Download not found', ERR_DOWNLOAD_NOT_FOUND);
	return $res[0]['destination'];
}


function delete_download(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$sql = "DELETE FROM downloadinfo WHERE \"ID\"=$id";
	$res = $db->execute_query($sql);
}


function get_download_name(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));

	$db->escape($id, TRUE);
	$query = "\"name\" FROM downloadinfo WHERE \"ID\"=$id";
	$res = $db->select_query($query, 1);
	if ($res === FALSE)
		throw new exception ('Download not found', ERR_DOWNLOAD_NOT_FOUND);
	return $res[0]['name'];
}


function delete_download_article (DatabaseConnection $db, $id, $status)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$db->escape($status, TRUE);
	$qry = "DELETE FROM downloadarticles WHERE \"downloadID\"=$id AND \"status\"=$status"; 
	$db->execute_query($qry);
}


function set_download_destination(DatabaseConnection $db, $id, $dir)
{
	assert(is_numeric($id));
	$db->escape($dir, TRUE);
	$db->escape($id, TRUE);
	$qry = "UPDATE downloadinfo SET \"destination\"=$dir WHERE \"ID\"=$id";
	$db->execute_query($qry);
}


function update_batch(DatabaseConnection $db, &$batch, $status = NULL)
{
	// $status = default value in case no specific value is set.

	foreach($batch as $row) {
		$id = $row['ID'];
		$db->escape($id, TRUE);
		if ($status === NULL AND !isset($row['dlstatus'])) {
			write_log('No idea what to set status to (update_batch)', LOG_ERR);
			throw new exception ('Need status for update_batch');
		}

		if (isset($row['dlstatus'])) {
			$thisstatus = $row['dlstatus'];
		}else{
			// Make sure the variable is also updated:
			$row['dlstatus'] = $status;
			$thisstatus = $status;
		}
		$db->escape($thisstatus, TRUE);
		$db->execute_query("UPDATE downloadarticles SET \"status\"=$thisstatus WHERE \"ID\"=$id");
	}
}


function set_password(DatabaseConnection $db, $userid, $password, $hashed = FALSE)
{
	assert(is_numeric($userid));
	$salt = generate_password(8);
	if ($hashed === FALSE)
		$hashpw = hash('sha256', $salt . $password . $salt);
	else 
		$hashpw = $password;
	$db->escape($salt, TRUE);
	$db->escape($hashpw, TRUE);
	$sql = "UPDATE users SET \"pass\"=$hashpw, \"salt\"=$salt WHERE \"ID\"=$userid";
	$db->execute_query($sql);
}


function GetAV(array $array, $key)
{
	if (isset($array[$key])) 
		return $array[$key];
	return '';
}


function format_setname($format_string, $chars, $value)
{
	$len = strlen($format_string);
	$setname = '';
	for  ($i = 0 ; $i < $len; $i++) {
		if ($format_string[$i] == '%') {
			if ($format_string[$i+1] == '%') {
				$i++;
				$setname .= '%';
				continue;
			} else {
				$rv = preg_match("/([\[{(]?)([-+]?\d*)(.)?([-+]?\d*)([$chars])([\])}]?)/", substr($format_string, $i+1), $matches);
				if (!$rv) {
					write_log("Unknown format character found", LOG_ERR);
					continue;
				} else {
					$str = preg_replace("/[$chars]/", 's', $matches[0]) ;
					$str = '%' . preg_replace("/[(\[\]}{)]/", '', $str) ;
					$match_len = strlen($matches[0]);
					if ($matches[1] != '') 
						$str = $matches[1] . $str . $matches[6];
					else if ($matches[6] != '')
						$match_len--;
					if (!isset($value[$matches[5]])) {
						write_log('Unknown format character found', LOG_ERR);
						continue;
					}
					if ($value[ $matches[5]] != '' )
						$setname .= sprintf("$str", $value[ $matches[5]]);
					$i += $match_len; 
				}
			}
		} else 
			$setname .= $format_string[$i];
	}
	return $setname;
}


function generate_set_name(DatabaseConnection $db, array $namevalues)
{
	return (trim(generate_set_name_1($db, $namevalues), "- \x0b\n\t\0\r"));
}


function generate_set_name_1(DatabaseConnection $db, array $namevalues)
{
	// When empty value is returned, the original set-name will be used.
	if (!isset($namevalues['binarytype'])) 
		return '';
	if (!isset($namevalues['name'])) 
		return '';
	$format_string = get_config($db, 'settype_'. $namevalues['binarytype']);
	// language = ????
	if ($format_string == '')
		$format_string = '%T %n';

	switch ($namevalues['binarytype']) {
	case SETTYPE_MOVIE: 
		$chars = 'TtnylsmaNqx';
		$name = GetAV($namevalues, 'name');
		$year = GetAV($namevalues, 'year');
		$lang = GetAV($namevalues, 'lang');
		$sublang = GetAV($namevalues, 'sublang');
		$movieformat = GetAV($namevalues, 'movieformat');
		$audioformat = GetAV($namevalues, 'audioformat');
		$note = GetAV($namevalues, 'note');
		$quality = GetAV($namevalues, 'quality');
		$xxx = GetAV($namevalues, 'xrated') == 1 ? 'XXX' : '';
		$values['n'] = $name;   // name
		$values['y'] = $year;   //
		$values['l'] = $lang;   //
		$values['s'] = $sublang;   
		$values['m'] = $movieformat;
		$values['a'] = $audioformat; 
		$values['N'] = $note;   //
		$values['x'] = $xxx;   // 
		$values['q'] = $quality;   // 
		$values['t'] = 'Movie';   // type 
		$values['T'] = ':_img_movie:';   //  image placeholder 
		break;
	case SETTYPE_ALBUM:
		$chars = 'TntyqfgN';
		$name = GetAV($namevalues, 'name');
		$year = GetAV($namevalues, 'year');
		$format = GetAV($namevalues, 'musicformat');
		$genre = GetAV($namevalues, 'musicgenre');
		$note = GetAV($namevalues, 'note');
		$quality = GetAV($namevalues, 'quality');
		$values['n'] = $name;
		$values['y'] = $year;
		$values['f'] = $format;
		$values['q'] = $quality;  
		$values['g'] = $genre;
		$values['N'] = $note;
		$values['t'] = 'Album';   // type 
		$values['T'] = ':_img_album:';   //  image placeholder 
		break;
	case SETTYPE_IMAGE: 
		$chars = 'TtnfgxqN';
		$name = GetAV($namevalues, 'name');
		$format= GetAV($namevalues, 'imageformat');
		$genre = GetAV($namevalues, 'imagegenre');
		$xxx = GetAV($namevalues, 'xrated') == 1 ? 'XXX' : '';
		$quality = GetAV($namevalues, 'quality');
		$note = GetAV($namevalues, 'note');
		$values['n'] = $name;
		$values['f'] = $format;
		$values['g'] = $genre;
		$values['x'] = $xxx;  
		$values['N'] = $note;
		$values['q'] = $quality;
		$values['t'] = 'Image';   // type
		$values['T'] = ':_img_image:';   //  image placeholder 
		break;
	case SETTYPE_SOFTWARE: 
		$chars = 'TnotNqx';
		$name = GetAV($namevalues, 'name');
		$os = GetAV($namevalues, 'os');
		$note = GetAV($namevalues, 'note');
		$xxx = GetAV($namevalues, 'xrated') == 1 ? 'XXX' : '';
		$quality = GetAV($namevalues, 'quality');
		$values['n'] = $name;
		$values['o'] = $os;
		$values['q'] = $quality;
		$values['x'] = $xxx;   //
		$values['N'] = $note;
		$values['t'] = 'Software';   // type
		$values['T'] = ':_img_software:';   // image placeholder 
		break;
	case SETTYPE_TVSERIES: 
		$chars = 'TtnemqaxNy';
		$name = GetAV($namevalues, 'name');
		$year = GetAV($namevalues, 'year');
		$eps = GetAV($namevalues, 'episode');
		$movieformat = GetAV($namevalues, 'movieformat');
		$audioformat = GetAV($namevalues, 'audioformat');
		$xxx = GetAV($namevalues, 'xrated') == 1 ? 'XXX' : '';
		$note = GetAV($namevalues, 'note');
		$quality = GetAV($namevalues, 'quality');

		$values['n'] = $name;   // name
		$values['y'] = $year;
		$values['e'] = $eps;   
		$values['m'] = $movieformat; 
		$values['a'] = $audioformat;
		$values['N'] = $note;   //
		$values['q'] = $quality;  
		$values['x'] = $xxx;   //
		$values['t'] = 'Series';   // type 
		$values['T'] = ':_img_series:';   //  image placeholder 
		break;
	case SETTYPE_EBOOK:
		$chars = 'TtnAfqgxy';
		$name = GetAV($namevalues, 'name');
		$author = GetAV($namevalues, 'author');
		$format = GetAV($namevalues, 'ebookformat');
		$xxx = GetAV($namevalues, 'xrated') == 1 ? 'XXX' : '';
		$genre = GetAV($namevalues, 'genericgenre');
		$year = GetAV($namevalues, 'year');
		$note = GetAV($namevalues, 'note');
		$quality = GetAV($namevalues, 'quality');

		$values['n'] = $name;   // name
		$values['q'] = $quality;
		$values['y'] = $year;
		$values['f'] = $format;
		$values['g'] = $genre;
		$values['N'] = $note;
		$values['A'] = $author;
		$values['x'] = $xxx;  
		$values['t'] = 'Ebook';   // type 
		$values['T'] = ':_img_ebook:';   //  image placeholder 
		break;
	case SETTYPE_GAME:
		$chars = 'TnotNqx';
		$name = GetAV($namevalues, 'name');
		$os = GetAV($namevalues, 'os');
		$note = GetAV($namevalues, 'note');
		$xxx = GetAV($namevalues, 'xrated') == 1 ? 'XXX' : '';
		$quality = GetAV($namevalues, 'quality');
		$values['n'] = $name;
		$values['o'] = $os;
		$values['q'] = $quality;   
		$values['x'] = $xxx;   // 
		$values['N'] = $note;
		$values['t'] = 'Game';   // type 
		$values['T'] = ':_img_game:';   //  image placeholder 
		break;
	case SETTYPE_DOCUMENTARY:
		$chars = 'TtnylsmaNqx';
		$name = GetAV($namevalues, 'name');
		$year = GetAV($namevalues, 'year');
		$lang = GetAV($namevalues, 'lang');
		$sublang = GetAV($namevalues, 'sublang');
		$movieformat = GetAV($namevalues, 'movieformat');
		$audioformat = GetAV($namevalues, 'audioformat');
		$note = GetAV($namevalues, 'note');
		$quality = GetAV($namevalues, 'quality');
		$xxx = GetAV($namevalues, 'xrated') == 1 ? 'XXX' : '';
		
		$values['n'] = $name;   // name
		$values['y'] = $year;   // 
		$values['l'] = $lang;   //
		$values['s'] = $sublang;   //
		$values['m'] = $movieformat;  
		$values['a'] = $audioformat; 
		$values['N'] = $note;   //
		$values['x'] = $xxx;   //
		$values['q'] = $quality;   // 
		$values['t'] = 'Documentary';   // type 
		$values['T'] = ':_img_documentary:';   //  image placeholder 
		break;
	case SETTYPE_TVSHOW:
		$chars = 'TtnmaxeNyq';
		$name = GetAV($namevalues, 'name');
		$year = GetAV($namevalues, 'year');
		$eps = GetAV($namevalues, 'episode');
		$quality = GetAV($namevalues, 'quality');
		$movieformat = GetAV($namevalues, 'movieformat');
		$audioformat = GetAV($namevalues, 'audioformat');
		$eps = GetAV($namevalues, 'episode');
		$xxx = GetAV($namevalues, 'xrated') == 1 ? 'XXX' : '';
		$note = GetAV($namevalues, 'note');

		$values['n'] = $name;   // name
		$values['y'] = $year;
		$values['e'] = $eps;   
		$values['m'] = $movieformat;   // name
		$values['a'] = $audioformat;   // name
		$values['N'] = $note;   // name
		$values['x'] = $xxx;   // name
		$values['q'] = $quality;   // name
        $values['t'] = 'TVShow';   // string
		$values['T'] = ':_img_tvshow:';   // image placeholder 
		break;
	case SETTYPE_UNKNOWN: 
	case SETTYPE_OTHER: 
	default:
		$chars = 'ntT';
		$values['n'] = GetAV($namevalues, 'name');   // name
		$values['t'] = 'Unknown';   // type 
		$values['T'] = ':_img_unknown:';   //  image placeholder
		break;
	}
	return format_setname($format_string, $chars, $values);
}


function set_download_name(DatabaseConnection $db, $dlid, $dlname)
{
	assert(is_numeric($dlid));
    $max_dl_name = get_config($db, 'max_dl_name');
    if (!is_numeric($max_dl_name) || $max_dl_name < 3)
        $max_dl_name = 10;
    $dlname = substr($dlname, 0, $max_dl_name); // DL names are no longer than X characters when auto-generated
	$db->escape($dlname, TRUE);
	$db->escape($dlid, TRUE);
	$sql = "UPDATE downloadinfo SET name=$dlname WHERE \"ID\" = $dlid";
	$db->execute_query($sql);
}


function set_download_size(DatabaseConnection $db, $dlid, $totalsize)
{
	assert(is_numeric($dlid) && is_numeric($totalsize));
	$db->escape($dlid, TRUE);
	$db->escape($totalsize, TRUE);
	$sql = "UPDATE downloadinfo SET \"size\"=$totalsize WHERE \"ID\" = $dlid";
	$db->execute_query($sql);
}


function get_start_time(DatabaseConnection $db, $dlid)
{
	assert(is_numeric($dlid));
	$db->escape($dlid, TRUE);
	$db->escape($starttime, TRUE);
	$sql = "SELECT start_time FROM downloadinfo WHERE \"ID\" = $dlid";
	$res = $db->execute_query($sql);
	if ($res === FALSE) 
		return FALSE;
	else
		return $res[0]['start_time'];
}


function set_start_time(DatabaseConnection $db, $dlid, $starttime)
{
	assert(is_numeric($dlid) && is_numeric($starttime));
	$db->escape($dlid, TRUE);
	$db->escape($starttime, TRUE);
	$sql = "UPDATE downloadinfo SET \"start_time\"=$starttime WHERE \"ID\" = $dlid";
	$db->execute_query($sql);
}


function get_dlinfo_status(DatabaseConnection $db, $dlid)
{
	assert(is_numeric($dlid));
	$db->escape($dlid, TRUE);
	$sql = "SELECT \"status\" FROM downloadinfo WHERE \"ID\" = $dlid";
	$res = $db->execute_query($sql);
	if ($res !== FALSE)
		return $res[0]['status'];
	else
		throw new exception ("Download not found $dlid", ERR_DOWNLOAD_NOT_FOUND);
}


function update_dlarticle_status(DatabaseConnection $db, $id, $status, $oldstatus=NULL, $operator=NULL)
{
	assert(is_numeric($id));
	$db->escape($id, TRUE);
	$db->escape($status, TRUE);
	$sql = "UPDATE downloadarticles SET \"status\" = $status WHERE \"downloadID\" = $id";
	if ($oldstatus != NULL) {
		if ($operator === NULL)
			$operator = '=';
		$db->escape($oldstatus, TRUE);

		$sql .= " AND status $operator $oldstatus";
	}
	$db->execute_query($sql);
}


function get_group_info(DatabaseConnection $db, $groupID)
{
	assert(is_numeric($groupID));
	$dbgrpid = $groupID;
	$db->escape($dbgrpid, TRUE);
	$res = $db->select_query(" * FROM groups WHERE \"active\" = '" . NG_SUBSCRIBED . "' AND \"ID\"=$dbgrpid", 1);

	if ($res === FALSE) 
		throw new exception("Group not found $groupID", ERR_GROUP_NOT_FOUND);
	return $res[0];
}


function set_feed_expire(DatabaseConnection $db, $id, $exp)
{
	assert(is_numeric($id) && is_numeric($exp));
	$db->escape($id, TRUE);
	$db->escape($exp, TRUE);
	$sql = "UPDATE rss_urls SET \"expire\" = $exp WHERE \"id\"=$id";
	$db->execute_query($sql);
}


function set_group_expire(DatabaseConnection $db, $id, $exp)
{
	assert(is_numeric($id) && is_numeric($exp));
	$db->escape($id, TRUE);
	$db->escape($exp, TRUE);
	$sql = "UPDATE groups SET \"expire\" = $exp WHERE \"ID\"=$id";
	$db->execute_query($sql);
}


function get_index_page_array($is_admin) 
{
	global $LN;
	$index_page_array1 = array(
		'browse' => $LN['menugroupsets'],
		'rsssets' => $LN['menursssets'],
		'transfers' => $LN['menudownloads'],
		'newsgroups'=> $LN['menunewsgroups'],
		'rssfeeds'=> $LN['menurssfeeds'],
		'viewfiles' => $LN['menuviewfiles'],
		'prefs'	=> $LN['menupreferences'],
		'stats' => $LN['menustats']
	);
	if ($is_admin) {
		$index_page_admin_array = array (
			'admin_config' => $LN['menuadminconfig'],
			'admin_control' => $LN['menuadmincontrol'],
			'admin_usenet_servers' => $LN['menuadminusenet'],
			'admin_log' => $LN['menuadminlog'],
			'admin_jobs' => $LN['menuadminjobs'],
			'admin_tasks' => $LN['menuadmintasks'],
			'admin_users' => $LN['menuadminusers'],
			'admin_buttons' => $LN['menuadminbuttons']
		);
	} else 
		$index_page_admin_array = array ();

	$index_page_array2 = array(
		'manual' => $LN['menuhelp'],	
		'faq' => $LN['menufaq'],
		'licence' => $LN['menulicence'],
		'debug' => $LN['menudebug']
	);

	$index_page_array = array_merge($index_page_array1, $index_page_admin_array, $index_page_array2);
	return $index_page_array;
}


function get_effective_username(DatabaseConnection$db, $username)
{
	$isadmin = is_admin($db, $username);
	return $isadmin?SUPER_USER:$username;
}


function urdd_add_sets(DatabaseConnection $db, array &$setdata, urdd_client &$uc, $dlid)
{
	assert(is_numeric($dlid));
	foreach ($setdata as $setid) {
		$uc->add_set_data($dlid, $setid);
	}
}


function make_range($from, $to)
{
	return "$from-$to";
}


function set_login_ip(DatabaseConnection $db, $ipaddr, $userid)
{
	assert(is_numeric($userid));
	$db->escape($ipaddr, TRUE);
	$db->escape($userid, TRUE);
	$sql = "UPDATE users SET \"ipaddr\" = $ipaddr WHERE  \"ID\" = $userid";
	$db->execute_query ($sql);
}


function set_last_login(DatabaseConnection $db, $last_login, $userid)
{
	assert(is_numeric($userid) && is_numeric($last_login));
	$db->escape($last_login, TRUE);
	$db->escape($userid, TRUE);
	$db->execute_query ("UPDATE users SET \"last_login\" = $last_login WHERE \"ID\" = $userid");
}


function set_last_active(DatabaseConnection $db, $last_active, $userid)
{
	assert(is_numeric($userid) && is_numeric($last_active));
	$db->escape($last_login, TRUE);
	$db->escape($userid, TRUE);
	$db->execute_query ("UPDATE users SET \"last_active\" = $last_active WHERE \"ID\" = $userid");
}


function add_stat_data(DatabaseConnection $db, $action, $value, $userid)
{
	if (!is_numeric($userid)) {
		assert (is_string($userid));
		$db->escape($userid, TRUE);
		$qry = "\"ID\" FROM users WHERE \"name\" = $userid";
		$rv = $db->select_query($qry);
		if ($rv === FALSE)
			throw new exception ("User not found: $userid");
		$userid = $rv[0]['ID'];
	}
	if (strtolower($value) == 'all')
		$value = 0;
	assert (is_numeric($value) && is_numeric($action) && is_numeric($userid));
	$timestamp = date('Y-n-j H:i:s', time());
	$db->insert_query('stats', array('userid', 'action', 'value', 'timestamp'), array($userid, $action, $value, $timestamp));
	return $db->get_last_id();
}


function get_username(DatabaseConnection $db, $userid)
{
	$db->escape($username, TRUE);
	$res = $db->execute_query("SELECT \"name\" FROM users WHERE \"ID\"=$userid");
	if ($res === FALSE)
		return FALSE;
	else 
		return $res[0]['name'];
}


function get_userid(DatabaseConnection $db, $username)
{
	$db->escape($username, TRUE);
	$res = $db->execute_query("SELECT \"ID\" FROM users WHERE \"name\"=$username");
	if ($res === FALSE)
		return FALSE;
	else 
		return $res[0]['ID'];
}


function check_last_lock(DatabaseConnection $db, $dlid) // return TRUE if not locked, false otherwise
{
	assert (is_numeric($dlid));
	$db->escape($dlid, TRUE);
	$res = $db->execute_query("SELECT \"lock\" FROM downloadinfo WHERE \"ID\"=$dlid");
	if ($res === FALSE)
		throw new exception ("Download not found $dlid", ERR_DOWNLOAD_NOT_FOUND);
	else 
		return ($res[0]['lock'] == 1 ? TRUE: FALSE);
}


function check_dl_lock(DatabaseConnection $db, $dlid) // return TRUE if not locked, false otherwise
{
	assert (is_numeric($dlid));
	$db->escape($dlid, TRUE);
	$res = $db->execute_query("SELECT \"lock\" FROM downloadinfo WHERE \"ID\"=$dlid");
	if ($res === FALSE)
		throw new exception ("Download not found $dlid", ERR_DOWNLOAD_NOT_FOUND);
	else 
		return ($res[0]['lock'] == 0 ? TRUE: FALSE);
}


function inc_dl_lock(DatabaseConnection $db, $dlid)
{
	assert (is_numeric($dlid));
	$db->lock(array('downloadinfo' =>'write'));
	try {
		$db->execute_query("UPDATE downloadinfo SET \"lock\" = \"lock\" + 1 WHERE \"ID\" = '$dlid'");
		$db->unlock();
	} catch (exception $e) {
		$db->unlock();
		throw $e;
	}
}


function dec_dl_lock(DatabaseConnection $db, $dlid)
{
	assert (is_numeric($dlid));
	$db->lock(array('downloadinfo' =>'write'));
	try {
		$db->execute_query("UPDATE downloadinfo SET \"lock\" = \"lock\" - 1 WHERE \"ID\" = '$dlid' AND \"lock\" > 0");
		$db->unlock();
	} catch (exception $e) {
		$db->unlock();
		throw $e;
	}
}


function get_all_userids(DatabaseConnection $db)
{
	$sql = "SELECT \"ID\" FROM users WHERE \"ID\">0";
	$res = $db->execute_query($sql);
	if ($res === FALSE)
		throw new exception('Cannot find any users');
	$ids = array();
	foreach($res as $row)
		$ids[] = $row['ID'];
	return $ids;
}


function get_all_users_full(DatabaseConnection $db)
{
	$sql = "SELECT * FROM users WHERE \"ID\">0";
	$res = $db->execute_query($sql);
	if ($res === FALSE)
		throw new exception('Cannot find any users');
	return $res;
}


function get_all_users(DatabaseConnection $db)
{
	$sql = "SELECT \"name\" FROM users WHERE \"ID\">0";
	$res = $db->execute_query($sql);
	if ($res === FALSE)
		throw new exception('Cannot find any users');
	$names = array();
	foreach($res as $row)
		$names[] = $row['name'];
	return $names;
}


function get_all_buttons($db)
{
    $sql = 'SELECT * FROM searchbuttons WHERE "id" > 0 ORDER BY "name" ASC';
    $res = $db->execute_query($sql);
    if ($res === FALSE)
        throw new exception('Cannot find any buttons');
    return $res;
}


function get_all_paths($base, $user='')
{
	static $paths = array(TMP_PATH, DONE_PATH, PREVIEW_PATH, NZB_PATH, SPOOL_PATH, SCRIPTS_PATH, POST_PATH);
	$path_list = array();
    foreach ($paths as $p) {
        $path = $base . $p . $user ;
        if (substr($path, -1) != '/')
            $path .= '/';
        $path_list[] = $path;
    }
	return $path_list;
}
	

function create_dir($path, $perms)
{
    echo_debug_function(DEBUG_SERVER, __FUNCTION__);
	$rv = TRUE;
    if (!is_dir($path)) {
        write_log("Creating directory $path", LOG_INFO);
		$rv = @mkdir($path, $perms, TRUE);
        @chmod($path, $perms);
	}
	return $rv;
}


function test_file_creation($path)
{
    $rand = '.urd_test_file' . mt_rand(10000, 99999);
    $rv = touch ($path . $rand); // create a test file
    if ($rv === FALSE)
        throw new exception("Could not create test file in directory $path", ERR_CONFIG_ERROR);
    unlink($path . $rand); // remove test file
}


function create_user_dirs(DatabaseConnection $db, $base, $user)
{
	assert (is_string($base) && is_dir($base) && is_string($user));
	$paths = get_all_paths($base, $user);
	foreach($paths as $p) {
		$rv = create_dir($p, 0775);
		if ($rv === FALSE)
            throw new exception ("Could not create directory $p");
        set_group($db, $p); // change the group if config is set
        if ((!is_dir($p) || !is_writable($p))) // check if it valid now
            throw new exception("Directory not accessible: $p", ERR_CONFIG_ERROR);

        test_file_creation($p);
	}
}


function create_required_user_dirs(DatabaseConnection $db, $base)
{
	$users = get_all_users($db);
	foreach ($users as $user) {
		$path = $base . $user;
		$rv = create_dir($path, 0775);
		if ($rv === FALSE)
			throw new exception ("Could not create directory $path");
		set_group($db, $path);
        if ((!is_dir($path) || !is_writable($path))) // check if it valid now
            throw new exception("Directory not accessible: $path", ERR_CONFIG_ERROR);
	}
}


function update_settings(DatabaseConnection $db, $userid)
{
	assert (is_numeric($userid));
	if (get_username($db, $userid) === FALSE)
		throw new exception ('User not found', ERR_INVALID_USERNAME);
	if ($userid == SUPER_USERID) {
		$default =  get_default_config();
		$config = load_config($db);
		$diff = array_diff_key($default, $config);
	} else {
		$default = get_default_prefs();
		$prefs = load_prefs_clean($db, $userid);
		$diff = array_diff_key($default, $prefs);
	}
	$cnt = count($diff);
	if ($cnt == 0)
		return 0;

	$arr = array();
	foreach ($diff as $var=>$val)
		$arr[] = array ($userid, $var, $val);

	$db->insert_query('preferences', array ('userID', 'option', 'value'), $arr);
	return $cnt;
}


function check_prefs(DatabaseConnection $db, $sendmail=TRUE)
{
	assert(is_bool($sendmail));
	$qry = 'SELECT "ID" FROM users';
	$res = $db->execute_query($qry);
	if ($res === FALSE)
		throw new exception ('No users found', ERR_NO_USERS);
	foreach($res as $row) {
		$id = $row['ID'];
		$cnt = update_settings($db, $id);
		if ($cnt > 0) {
			if ($id == SUPER_USERID)
				write_log('New configuration parameters added; check admin/config', LOG_WARNING);
			else {
				write_log("New preferences added for user $id; check preferences", LOG_WARNING);
				if ($sendmail === TRUE)
					mail_update_preferences($db, $id);
			}
		}
	}
}


function ch_group($fn, $group)
{
	$fgroup = filegroup($fn);
	if ($fgroup === FALSE)
		write_log("Cannot get group of $fn", LOG_ERR);
	$fgroup = posix_getgrgid($fgroup);
	if ($fgroup['name'] != $group) { // we don't have to change it if it is already set :D
		$rv = @chgrp($fn, $group);
		if ($rv === FALSE) {
			write_log("Cannot set groups, probably insufficient right to chgrp() $fn to $group", LOG_ERR);
			return FALSE;
		}
	}
	return TRUE;
}


function set_dirgroups($dir, $group)
{
	assert(is_string($dir) && $dir != '' && $group != '');
	$files = glob ($dir . '/*', GLOB_NOSORT);
	foreach ($files as $f) {
		ch_group($f, $group);
		if (is_dir($f)) 
			set_dirgroups($f, $group);
	}
}


function set_group(DatabaseConnection $db, $dir)
{
	echo_debug_function(DEBUG_SERVER, __FUNCTION__);
	try {
		$group = get_config($db, 'group');
	} catch (exception $e) {
		write_log('Cannot set group; db value not set', LOG_NOTICE);
		return;
	}
	if ($group == '')  // if it is blank wo don't set it
		return;
	if (posix_getgrnam($group) === FALSE) { // check if it is a valid group
		write_log('Invalid group specifid. Check /etc/groups', LOG_ERR);
		return;
	}
	ch_group($dir, $group); // change the group on the path
	set_dirgroups($dir, $group); // and do it recuresively
}


function get_salt(DatabaseConnection $db, $username)
{
	assert ($username != '');
	$sql = "\"salt\" FROM users WHERE \"name\" = '$username' AND \"active\" = '". USER_ACTIVE ."'";
	$res = $db->select_query($sql, 1);
	if ($res === FALSE) // no valid user found
		throw new exception('User not found', ERR_NO_SUCH_SERVER);
	else 
		return $res[0]['salt'];
}


function get_rss_info(DatabaseConnection $db, $id, $subscribed=TRUE)
{
	assert(is_numeric($id) && is_bool($subscribed));
	$db->escape($id, TRUE);
	$qry = " * FROM rss_urls WHERE \"id\" = $id";
	if ($subscribed)
		$qry .= " AND \"subscribed\" = '" . RSS_SUBSCRIBED . "'";
	$res = $db->select_query($qry, 1);
	if (!is_array($res)) 
		throw new exception('Error: rss feed not found', ERR_RSS_NOT_FOUND);
	return $res[0];
}


function in_setdata($setID, $type, array $setdata)
{
	foreach ($setdata as $set){
		if ($set['setid'] == $setID && $set['type'] == $type)
			return TRUE;
	}
	return FALSE;
}


function validate_url($url)
{
	assert(is_string($url));
	$res = preg_match ('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/[a-z0-9.@\-_:/~%&;\[\]]*)?/$|i', $url);
	return $res == 1;
}


function delete_rss_feed(DatabaseConnection $db, $id)
{
	assert(is_numeric($id));
	$rss_info = get_rss_info($db, $id, FALSE);
	$type = USERSETTYPE_RSS;
	$sql = "DELETE FROM usersetinfo WHERE \"setID\" in (SELECT \"setid\" FROM rss_sets WHERE \"rss_id\" = '$id') AND \"type\" = '$type'";
	$res = $db->execute_query($sql);
	$sql = "DELETE FROM extsetdata WHERE \"setID\" in (SELECT \"setid\" FROM rss_sets WHERE \"rss_id\" = '$id')  AND \"type\" = '$type'";
	$res = $db->execute_query($sql);

	$sql = "DELETE FROM rss_urls WHERE \"id\" = '$id'";
	$db->execute_query($sql);
	$sql = "DELETE FROM rss_sets WHERE \"rss_id\" = '$id'";
	$db->execute_query($sql);
	delete_cache_entry($rss_info['url'], get_magpie_cache_dir() );
}


function real_mime_content_type(DatabaseConnection $db, $file, $force_file=FALSE)
{
	$file_prog = get_config($db, 'file_path');
	if (function_exists('mime_content_type') && $force_file === FALSE) 
		return mime_content_type($file);
	else {
		$file = my_escapeshellarg($file);
		$mime_type = @exec("$file_prog -bi $file 2> /dev/null", $dummy, $rv);
		if ($rv != 0)
			return 'text/html';
		else
			return trim($mime_type);
	}
}


function copy_files($src, $dest, array $files, $match_path = NULL)
{
    // copy filse from dir src to dir dest, matching file name specs in $files; where src and dest must be a subdir of match_path 
    $src = realpath($src);
    $dest = realpath($dest);
    if ($match_path !== NULL) {
        $match_path = realpath($match_path);
        if ($match_path == FALSE || substr($src, 0, strlen($match_path)) != $match_path ||substr($dest, 0, strlen($match_path)) != $match_path ) 
            return FALSE;
    }
    if ($src == FALSE || $dest == FALSE)
        return FALSE;
    foreach($files as $f) {
        $filenames = glob($src . '/' . $f);
        //var_dump($filenames, $dest);
        foreach ($filenames as $filename) {
            if ($match_path !== NULL && substr($filename, 0, strlen($match_path)) != $match_path) 
                return FALSE;
            if (is_file($filename))
                copy($filename, $dest . '/' . basename($filename));
        }
    }
    return TRUE;
}


function unformat_size($val)
{
    $val = trim($val);
    $last = strtolower(substr($val, -1));
    $val = (int) $val;

    if($last == 'g')
        $val = $val * 1024 * 1024 * 1024;
    if($last == 'm')
        $val = $val * 1024 * 1024;
    if($last == 'k')
        $val = $val *1024;

    return $val;
}


if (!function_exists('memory_get_peak_usage')) {
    // needed as this function is only defined from php 5.2.0 and we support >= 5.1.2
    function memory_get_peak_usage()
    {
        return 0;
    }
}


function get_stat_id(DatabaseConnection $db, $dlid, $is_post=FALSE)
{
    assert(is_numeric($dlid));
    $db->escape($dlid, TRUE);
    list($table, $idrow) = ($is_post? array('postinfo', 'id') : array('downloadinfo', 'ID'));
    $sql = "SELECT \"stat_id\" FROM $table WHERE \"$idrow\" = $dlid";
    $res = $db->execute_query($sql);
    if ($res === FALSE)
        return FALSE;
    else
        return $res[0]['stat_id'];
}


function set_stat_id(DatabaseConnection $db, $dlid, $stat_id, $is_post=FALSE)
{
    assert(is_numeric($dlid) && is_numeric($stat_id));
    $db->escape($dlid, TRUE);
    $db->escape($stat_id, TRUE);
    list($table, $idrow) = ($is_post? array('postinfo', 'id') : array('downloadinfo', 'ID'));
    $sql = "UPDATE $table SET \"stat_id\" = $stat_id WHERE \"$idrow\" = $dlid";
    $res = $db->execute_query($sql);
    if ($res === FALSE)
        return FALSE;
    else
        return $res[0]['stat_id'];
}


function update_dlstats(DatabaseConnection $db, $stat_id, $value)
{
    assert(is_numeric($stat_id) && is_numeric($value));
    if ($stat_id == 0) 
        return;
    $db->escape($stat_id, TRUE);
    $db->escape($value, TRUE);
    $sql = "UPDATE stats SET \"value\"= \"value\" + $value WHERE \"id\" = $stat_id";
    $res = $db->execute_query($sql);
}


function get_groupid_for_set(DatabaseConnection $db, $setid)
{
    $db->escape($setid, TRUE);
    $sql = " \"groupID\" FROM setdata WHERE \"ID\" = $setid";
    $res = $db->select_query($sql, 1);
    if ($res === FALSE)
        throw new exception('Group not found', ERR_GROUP_NOT_FOUND);
    else 
        return $res[0]['groupID'];
}


function get_feedid_for_set(DatabaseConnection $db, $setid)
{
    $db->escape($setid, TRUE);
    $sql = " \"rss_id\" FROM rss_sets WHERE \"setid\" = $setid";
    $res = $db->select_query($sql, 1);
    if ($res === FALSE)
        throw new exception('Feed not found', ERR_FEED_NOT_FOUND);
    else 
        return $res[0]['groupID'];
}


function store_merge_sets_data(DatabaseConnection $db, $new_setID, $old_setID, $type, $commit = ESI_COMMITTED)
{
    echo_debug_function(DEBUG_SERVER, __FUNCTION__);
    // Does info exist?
    $db->escape($type);
    $db->escape($setID);
    $db->escape($old_setID);
    $sql = " * FROM merged_sets WHERE \"old_setid\" = '$old_setID' AND \"type\" = '$type'";
    $res = $db->select_query($sql);
    // Check if the stored field exists
    if ($res === FALSE) 
        $db->insert_query('merged_sets', array ('new_setid', 'old_setid', 'type', 'committed'), array($new_setID, $old_setID, $type, $commit));
}


function store_ext_setdata(DatabaseConnection $db, $setID, array $save, $type, $commit = ESI_COMMITTED)
{
    echo_debug_function(DEBUG_SERVER, __FUNCTION__);
    // Generate the setname:
    $newsetname = generate_set_name($db, $save);
    if ($newsetname == '') return FALSE; // We have no idea what kind of upload this is. So we don't set anything.
    $unsafensn = $newsetname;
    $db->escape($newsetname, TRUE);
    $db->escape($setID);
    // Save the individual extsetinfo's:
    foreach ($save as $name => $value) {
        if ($name == 'MERGE_SET')
            store_merge_sets_data($db, trim($value), $setID, $type, $commit);
        else {
            $value_x = trim($value);
            $db->escape($name);

            $db->escape($type);
            // Does info exist?
            $sql = " * FROM extsetdata WHERE \"setID\" = '$setID' AND \"name\" = '$name' AND \"type\" = '$type'";
            $res = $db->select_query($sql);

            // Check if the stored field exists
            if ($res === FALSE) {
                $db->insert_query('extsetdata', array ('setID', 'name', 'value', 'committed', 'type'), array($setID, $name, $value_x, $commit, $type) );
            }
        }
    }

    // Store the new setname based on the extsetinfo:
    $sql = " * FROM extsetdata WHERE \"setID\" = '$setID' AND \"name\" = 'setname' AND \"type\" = '$type'";
    $res = $db->select_query($sql);
    if ($res === FALSE) {
        $sql = "INSERT INTO extsetdata (\"setID\", \"name\", \"value\", \"committed\", \"type\") VALUES ('$setID', 'setname', $newsetname, $commit, $type)";
    } else {
        $sql = "UPDATE extsetdata SET \"value\" = $newsetname, \"committed\" = $commit WHERE \"setID\" = '$setID' AND \"name\" = 'setname' AND \"type\" = '$type'";
    }
    $db->execute_query($sql);

    // Return newsetname so we can use ajax to show it in the browse page without reloading it.
    return $unsafensn; // Unsafe version, we don't want the added quotes thank you.
}


function start_urdd()
{
    global $db;
    $prefs = load_config($db); 

    $trickle = my_escapeshellcmd($prefs['trickle_path']);
    $urdd = my_escapeshellcmd($prefs['urdd_path']);
    $urdd_pars = my_escapeshellarg($prefs['urdd_pars'], FALSE);
    if ($urdd == '') {
        write_log('Urdd not found', LOG_ERR);
        exit;
    }
    $bandwidth = $prefs ['maxdl'];
    // Command to a variable, use full path 
    $shaping = ( $prefs['shaping'] == 1 );
    if (!is_numeric($bandwidth) || $bandwidth <= 0 ){
        write_log('Invalid value for bandwidth', LOG_NOTICE);
        $shaping = FALSE;
    }
    if ($trickle == '' || $trickle == 'off')
        $shaping = FALSE;

    if ($shaping === FALSE)
        $cmd = "$urdd -D $urdd_pars";
    else
        $cmd = "$trickle -d $bandwidth $urdd -D $urdd_pars";
    write_log("Starting URDD: $cmd");
    echo_debug($cmd, DEBUG_CLIENT);
    exec($cmd);
    // Give it some time to start before we check if it's running etc:
    usleep(500000);
}


function get_all_feeds(DatabaseConnection $db)
{
    $sql = 'SELECT * FROM rss_urls';
    $rv = $db->execute_query($sql);
    if ($rv === FALSE)
        throw new exception('No feeds found');
    return $rv;
}


function get_all_active_groups(DatabaseConnection $db)
{
    $sql = 'SELECT * FROM groups WHERE "active" = \'' . NG_SUBSCRIBED . "'";
    $rv = $db->execute_query($sql);
    if ($rv === FALSE)
        throw new exception('No groups found');
    return $rv;
}


function gmp_min($a, $b)
{
    return (gmp_cmp($a, $b) < 0) ? $a : $b;
}


function gmp_max($a, $b)
{
    return (gmp_cmp($a, $b) > 0) ? $a : $b;
}


function get_magpie_cache_dir()
{
    $pathu = realpath(dirname(__FILE__));
    $path = realpath("$pathu/libs/magpierss/cache");
    return $path;
}


function rss_url_name_exists(DatabaseConnection $db, $name, $id=NULL)
{
    // $name is already escaped
    $sql = "SELECT count(\"id\") AS cnt FROM rss_urls WHERE \"name\" = '$name'";
    if ($id !== NULL) {
        $db->escape($id, TRUE);
        $sql .= " AND \"id\" != $id";
    }
    $res = $db->execute_query($sql);
    if ($res === FALSE || $res[0]['cnt'] == 0)
        return FALSE;
    else 
        return TRUE;
}


function update_rss_url(DatabaseConnection $db, $name, $url, $subscribed, $expire, $username, $password, $id)
{
    $db->escape($name);
    $db->escape($expire);
    if (rss_url_name_exists($db, $name, $id))
        die_html('RSS Feed name exists');
    $rv = validate_url($url);
    if ($rv != '')
        die_html($rv['msg']);

    $db->escape($url);
    $db->escape($id);
    $sql = "UPDATE rss_urls SET \"name\" = '$name', \"url\" = '$url', \"subscribed\"= '$subscribed', " .
        "\"expire\"='$expire', \"username\"='$username', \"password\"='$password' WHERE \"id\" = '$id'";
    $res = $db->execute_query($sql);
}


function add_rss_url(DatabaseConnection $db, $name, $url, $subscribed, $expire, $username, $password)
{
    $db->escape($name);
    $db->escape($url);
    $db->escape($expire);

    if (rss_url_name_exists($db, $name)) {
        die_html('RSS Feed name exists');
    }
    $rv = validate_url($url);
    if ($rv != '') {
        die_html($rv['msg']);
    }
    try {
        $db->insert_query('rss_urls', array('name', 'url', 'subscribed', 'expire', 'username', 'password'), array($name, $url, $subscribed, $expire, $username, $password));
    } catch (exception $e) {
        die_html('Insert failed: ' . $e->getmessage());
    }
}


function clear_all_feeds(DatabaseConnection $db, $userID)
{
    assert (is_numeric($userID));
    $uprefs = load_config($db);
    $uc = new urdd_client($uprefs['urdd_host'], $uprefs['urdd_port'], $userID);
    try {
        $feedinfo = get_all_feeds($db);
        foreach ($feedinfo as $feed) {
            $id = $feed['id'];
            if ($feed['subscribed'] == RSS_SUBSCRIBED) {
                $uc->cancel(get_command(COMMAND_UPDATE_RSS) . " $id");
                $uc->cancel(get_command(COMMAND_EXPIRE_RSS) . " $id");
                remove_schedule($db, $uc, $id, COMMAND_UPDATE_RSS);
                $uc->unsubscribe_rss($id);
            }
            delete_rss_feed($db, $id);
        }
    } catch (exception $e) { 
        //echo $e->getMessage();
	}
    $uc->disconnect();
}


function clear_all_groups(DatabaseConnection $db, $userID)
{
    assert (is_numeric($userID));
	$uprefs = load_config($db);
	$uc = new urdd_client($uprefs['urdd_host'], $uprefs['urdd_port'], $userID);
	try {
		$groupinfo = get_all_active_groups($db);
        foreach ($groupinfo as $gr) {
            $id = $gr['ID'];
			$uc->cancel(get_command(COMMAND_UPDATE) . " $id");
			$uc->cancel(get_command(COMMAND_EXPIRE) . " $id");
			$uc->cancel(get_command(COMMAND_GENSETS) . " $id");
			remove_schedule($db, $uc, $id, COMMAND_UPDATE);
			$uc->unsubscribe($id);
		}
	} catch (exception $e) { 
		//echo $e->getMessage();
	}
    $uc->disconnect();
}


function set_all_feeds(DatabaseConnection $db, array $settings, $userID)
{
    assert (is_numeric($userID));
	global $periods;
	$subscribed = RSS_SUBSCRIBED;
	$db->escape($subscribed, TRUE);
	$uprefs = load_config($db);
	$uc = new urdd_client($uprefs['urdd_host'], $uprefs['urdd_port'], $userID);
    foreach ($settings as $set) {
        $name = $set['name'];
        $url = $set['url'];
        $expire = $set['expire'];
        $username = $set['username'];
        $password = $set['password'];
        $subscribed = $set['subscribed'];
		add_rss_url($db, $name, $url, 0, $expire, $name, $password);
        $rss_id = feed_by_name($db, $set['name']);
        if ($subscribed) {
            $uc->subscribe_rss($rss_id, $set['expire']);
            $refresh_time = $set['refresh_time'];
            $refresh_period = $set['refresh_period'];
            if ($refresh_period > 0) {
                $period = $periods->get($refresh_period);
                if ($period !== NULL) {
                    $time = (floor($refresh_time / 60)) . ':' . ($refresh_time % 60); 
                    set_period_rss($uc, $db, $rss_id, $time, $period->interval, $refresh_time, $refresh_period);
                }
            }
        }
	}
    $uc->disconnect();
}


function set_all_groups(DatabaseConnection $db, array $settings, $userID)
{
    assert (is_numeric($userID));
	global $periods;
	$subscribed = NG_SUBSCRIBED;
	$db->escape($subscribed, TRUE);
	$uprefs = load_config($db);
	$uc = new urdd_client($uprefs['urdd_host'], $uprefs['urdd_port'], $userID);
	foreach ($settings as $set) {
		$groupid = group_by_name($db, $set['groupname']);
		$uc->subscribe($groupid, $set['expire']);
		$refresh_time = $set['refresh_time'];
        $refresh_period = $set['refresh_period'];
        if ($refresh_period > 0) {
            $period = $periods->get($refresh_period);
            if ($period !== NULL) {
                $time = (floor($refresh_time / 60)) . ':' . ($refresh_time % 60); 
                set_period($uc, $db, $groupid, $time, $period->interval, $refresh_time, $refresh_period);
            }
        }
	}
    $uc->disconnect();
}


function clear_all_usenet_servers(DatabaseConnection $db)
{
    set_config($db, 'preferred_server', 0);
    try {
        $srv = get_all_usenet_servers($db, FALSE);
        foreach ($srv as $s) 
            delete_usenet_server($db, $s['id']); 
    } catch (exception $e) {
    }
}


function set_all_usenet_servers(DatabaseConnection $db, array $settings)
{
    $pref_server = $settings['preferred_server'];

    foreach($settings as $setting) {
        if (is_array($setting)) {
            $id = add_usenet_server($db, $setting['name'], $setting['hostname'], $setting['port'], $setting['secure_port'], $setting['threads'], 
            $setting['connection'], $setting['authentication'], $setting['username'], $setting['password'], $setting['priority'], $setting['compressed_headers'], $setting['posting']);
            if ($setting['name'] == $pref_server && $pref_server != '')
                set_config($db, 'preferred_server', $id);
        }
    }
}


function clear_all_users(DatabaseConnection $db)
{
    $users = get_all_userids($db);
    foreach ($users as $userid)
        //if ($userid != 2) // XXX must remove -- for testing only
            delete_user($db, $userid);
}


function set_all_users(DatabaseConnection $db, array $users, array $settings)
{

    foreach ($users as $user) {
        try {
            add_user($db, $user['username'], $user['fullname'], $user['email'], $user['password'], $user['isadmin'], $user['active'], $user['rights'], FALSE , $user['salt']);
        if (isset($settings[$user['username']])) {
            $userID = get_userid($db, $user['username']);
            reset_pref($db, $userID); // restore the default settings
            set_prefs($db, $userID, $settings[$user['username']]); // overwrite with loaded settings
        }
        } catch (exception $e) {
            // what do we do now?
        }
    }
}


function clear_all_buttons(DatabaseConnection $db)
{
    try {
        $buttons = get_all_buttons($db);
        foreach ($buttons as $button)
            delete_button($db, $button['id']);
    } catch (exception $e)
    {
    }
}


function set_all_buttons(DatabaseConnection $db, array $buttons)
{
    foreach($buttons as $button) {
        add_button($db, $button['name'], $button['image_url'], $button['search_url']);
    }

}


function perms_to_string($perms)
{
        assert(is_numeric($perms));
        if (($perms & 0xC000) == 0xC000) { // Socket
                $info = 's';
        } elseif (($perms & 0xA000) == 0xA000) { // Symbolic Link
                $info = 'l';
        } elseif (($perms & 0x8000) == 0x8000) { // Regular
                $info = '-';
        } elseif (($perms & 0x6000) == 0x6000) { // Block special
                $info = 'b';
        } elseif (($perms & 0x4000) == 0x4000) { // Directory
                $info = 'd';
        } elseif (($perms & 0x2000) == 0x2000) { // Character special
                $info = 'c';
        } elseif (($perms & 0x1000) == 0x1000) { // FIFO pipe
                $info = 'p';
        } else { // Unknown
                $info = 'u';
        }

        // Owner
        $info .= (($perms & 0x0100) ? 'r' : '-');
        $info .= (($perms & 0x0080) ? 'w' : '-');
        $info .= (($perms & 0x0040) ?  (($perms & 0x0800) ? 's' : 'x' ) : (($perms & 0x0800) ? 'S' : '-'));

        // Group
        $info .= (($perms & 0x0020) ? 'r' : '-');
        $info .= (($perms & 0x0010) ? 'w' : '-');
        $info .= (($perms & 0x0008) ?  (($perms & 0x0400) ? 's' : 'x' ) : (($perms & 0x0400) ? 'S' : '-'));

        // World
        $info .= (($perms & 0x0004) ? 'r' : '-');
        $info .= (($perms & 0x0002) ? 'w' : '-');
        $info .= (($perms & 0x0001) ?  (($perms & 0x0200) ? 't' : 'x' ) : (($perms & 0x0200) ? 'T' : '-'));
        return $info;
}


function get_admin_userid(DatabaseConnection $db)
{
    $sql = ' "ID" FROM users WHERE isadmin > 0 AND "ID" > 0';
    $res = $db->select_query($sql, 1);
    if ($res === FALSE)
        throw new exception ('No admin found');
    return $res[0]['ID'];
}


function deyenc(DatabaseConnection $db, $msg)
{
    for ($i = 0; $i < 10; $i++) {
        if (strstr($msg[$i], '=ybegin')) {
            // yydecode doesn't like size=-1 nor a missing name, so we need to fix that first
            // usually it is the first line anyway.
            $msg[$i] = preg_replace('/size=-?[0-9]+/', 'size=1000000', $msg[$i], 1); // tricky ... size needs to be big enough to hold the data as yydecode will truncate it 
            $msg[$i] .= ' name=temp'; // just a stupid basic name... anything goes really
            break;
        }
    }
    if ($i == 10) // ok something went terribly wrong. May not by yyencoded after all
        throw new exception('Not YYencoded?'); 
    
    $yenc = get_config($db, 'yydecode_path');
    $cmd = "/bin/sh -c '$yenc -v -b -i -l -o - ' ";
    // always returns an error cos the size is bogus
    $pipes = array();
    static $descriptorspec = array( 
        0 => array('pipe', 'r'), // where we will write to
        1 => array('pipe', 'w'), // well get the output from here
        2 => array('file', '/tmp/foobar', 'a') //   we don't want the errors
    );
    $process = proc_open($cmd, $descriptorspec, $pipes, '/tmp/', NULL, array('binary_pipes'));
    if ($process === FALSE || !is_resource($process)) {
        write_log('Failed to open pipe', LOG_ERR);
        throw new exception('Failed to open pipe');
    }
    //pipe to yenc
    foreach($msg as $line) {
        $rw = fwrite($pipes[0], $line. "\n");
        if ($rw === FALSE) {
            error_reporting($err_level);
            throw new exception('Write failed', ERR_PIPE_ERROR);
        }
    }
    pclose($pipes[0]);
    $data = stream_get_contents($pipes[1]); // get all the output as one string
    if ($data === FALSE)
        throw new exception('Could not read from pipe', ERR_PIPE_ERROR);
    if ($data == '')
        write_log('something must gone wrong yydecoden', LOG_NOTICE);

    pclose($pipes[1]);
    proc_close($process);
    
    return $data;
}


function real_file_size($file)
{// filesizes for large files aren't handled properly by PHP (douchebags!) so use an external command instead
	// fortunately all *nix version use stat in the same way :(
	if(!file_exists($file))
		return -1;
	switch (PHP_OS) {
	case 'Linux':
		$file = my_escapeshellarg($file);
		$fs = exec ("stat -c %s $file", $ov);
		break;
	case 'FreeBSD':
		$file = my_escapeshellarg($file);
		$fs = exec ("stat -f %z $file", $ov);
		break;
	case 'WIN':
	default:
		$fs = filesize($file);
		break;
	}
	return $fs;
}


function dirsize($path)
{
    /* Updated from Aidan Lister <aidan@php.net>'s code */

    // Init
    $size = 0;

    // Trailing slash
    if (substr($path, -1, 1) !== DIRECTORY_SEPARATOR) 
        $path .= DIRECTORY_SEPARATOR;
    
    // Sanity check
    if (is_file($path)) 
        return real_file_size($path);
    elseif (!is_dir($path)) 
        return FALSE;

    // Iterate queue
    $queue = array($path);
    for ($i = 0, $j = count($queue); $i < $j; ++$i) {
        // Open directory
        $parent = $i;
        if (is_dir($queue[$i]) && $dir = @dir($queue[$i])) {
            $subdirs = array();
            while (FALSE !== ($entry = $dir->read())) {
                // Skip pointers
                if ($entry == '.' || $entry == '..') 
                    continue;
                
                // Get list of directories or filesizes
                $path = $queue[$i] . $entry;
                if (is_dir($path)) {
                    $path .= DIRECTORY_SEPARATOR;
                    $subdirs[] = $path;
                } elseif (is_file($path)) 
                    $size += real_file_size($path);
            }

            // Add subdirectories to start of queue
            unset($queue[0]);
            $queue = array_merge($subdirs, $queue);

            // Recalculate stack size
            $i = -1;
            $j = count($queue);

            // Clean up
            $dir->close();
            unset($dir);
        }
    }
    return $size;
}


function update_dlinfo_groupid(DatabaseConnection $db, $dlid, $groupid)
{
    assert(is_numeric($dlid) && is_numeric($groupid));
    $db->escape($dlid);
    $db->escape($groupid);
    $sql = "UPDATE downloadinfo SET \"groupid\" = '$groupid' WHERE \"ID\" = '$dlid' AND groupid = 0";
    $db->execute_query($sql);
}


function get_groupid_dlinfo(DatabaseConnection $db, $dlid)
{
    assert(is_numeric($dlid));
    $db->escape($dlid);
    $sql = "SELECT \"groupid\" FROM downloadinfo WHERE \"ID\" = '$dlid'";
    $res = $db->execute_query($sql);
    if (!isset($res[0]['groupid']))
        return 0;
    return $res[0]['groupid'];
}


function get_smart_timestamp($timestamp)
{
    global $LN;
    if (date('z', time()) == date('z', $timestamp)) // we only return the time, since it is today
        if (isset($LN['timeformat']))
            return date($LN['timeformat'], $timestamp);
        else 
            return date('H:i:s', $timestamp);
    else { // need the date too now
        if (isset($LN['dateformat']) && isset($LN['timeformat']))
            return date($LN['dateformat'] . ' ' . $LN['timeformat'] , $timestamp);
        else 
            return date('d-m-Y H:i:s', $timestamp);
    }
}


function get_post_name(DatabaseConnection $db, $ID)
{
    $db->escape($ID, TRUE);
    $sql = "\"subject\" FROM postinfo WHERE \"id\" = $ID";
    $res = $db->select_query($sql);
    if (isset($res[0]['subject']) )
        return $res[0]['subject'];
    else 
        throw new exception ('Post not found');
}


?>
